mirror of https://github.com/VLSIDA/OpenRAM.git
fix merge conflicts
This commit is contained in:
commit
df4a231c04
|
|
@ -1,8 +1,12 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*~
|
*~
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
*.pyc
|
*.pyc
|
||||||
*.aux
|
*.aux
|
||||||
*.out
|
*.out
|
||||||
*.toc
|
*.toc
|
||||||
*.synctex.gz
|
*.synctex.gz
|
||||||
**/model_data
|
**/model_data
|
||||||
|
outputs
|
||||||
|
technology/freepdk45/ncsu_basekit
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
before_script:
|
before_script:
|
||||||
- . /home/gitlab-runner/setup-paths.sh
|
- . /home/gitlab-runner/setup-paths.sh
|
||||||
- export OPENRAM_HOME="`pwd`/compiler"
|
- export OPENRAM_HOME="`pwd`/compiler"
|
||||||
- export OPENRAM_TECH="`pwd`/technology"
|
- export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech"
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
|
|
@ -25,6 +25,15 @@ scn4m_subm:
|
||||||
- .coverage.*
|
- .coverage.*
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
|
# s8:
|
||||||
|
# stage: test
|
||||||
|
# script:
|
||||||
|
# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8
|
||||||
|
# artifacts:
|
||||||
|
# paths:
|
||||||
|
# - .coverage.*
|
||||||
|
# expire_in: 1 week
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
stage: coverage
|
stage: coverage
|
||||||
script:
|
script:
|
||||||
|
|
|
||||||
23
HINTS.md
23
HINTS.md
|
|
@ -37,7 +37,15 @@ to run Calibre or Magic+Netgen.
|
||||||
To debug, you will need a layout viewer. I prefer to use Glade
|
To debug, you will need a layout viewer. I prefer to use Glade
|
||||||
on my Mac, but you can also use Calibre, Magic, etc.
|
on my Mac, but you can also use Calibre, Magic, etc.
|
||||||
|
|
||||||
1. Calibre
|
1. Klayout
|
||||||
|
|
||||||
|
You can view the designs in [Klayout](https://www.klayout.de/) with the configuration
|
||||||
|
file provided in the tech directories. For example,
|
||||||
|
```
|
||||||
|
klayout temp.gds -l /home/vagrant/openram/technology/freepdk45/tf/FreePDK45.lyp
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Calibre
|
||||||
|
|
||||||
Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file:
|
Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file:
|
||||||
```
|
```
|
||||||
|
|
@ -52,10 +60,9 @@ on my Mac, but you can also use Calibre, Magic, etc.
|
||||||
|
|
||||||
In the viewer ">" opens the layout down a level.
|
In the viewer ">" opens the layout down a level.
|
||||||
|
|
||||||
2. Glade
|
3. Glade
|
||||||
|
|
||||||
You can view errors in Glade as well. I like this because it is on my laptop.
|
You can view errors in [Glade](http://www.peardrop.co.uk/glade/) as well.
|
||||||
You can get it from: http://www.peardrop.co.uk/glade/
|
|
||||||
|
|
||||||
To remote display over X windows, you need to disable OpenGL acceleration or use vnc
|
To remote display over X windows, you need to disable OpenGL acceleration or use vnc
|
||||||
or something. You can disable by adding this to your .bashrc in bash:
|
or something. You can disable by adding this to your .bashrc in bash:
|
||||||
|
|
@ -82,16 +89,16 @@ ui().importCds("default",
|
||||||
To load the errors, you simply do Verify->Import Calibre Errors select
|
To load the errors, you simply do Verify->Import Calibre Errors select
|
||||||
the .results file from Calibre.
|
the .results file from Calibre.
|
||||||
|
|
||||||
3. Magic
|
4. Magic
|
||||||
|
|
||||||
Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules
|
Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules
|
||||||
and Magic from: http://opencircuitdesign.com/
|
and [Magic](http://opencircuitdesign.com/)
|
||||||
|
|
||||||
When running DRC or extraction, OpenRAM will load the GDS file, save
|
When running DRC or extraction, OpenRAM will load the GDS file, save
|
||||||
the .ext/.mag files, and export an extracted netlist (.spice).
|
the .ext/.mag files, and export an extracted netlist (.spice).
|
||||||
|
|
||||||
4. It is possible to use other viewers as well, such as:
|
5. It is possible to use other viewers as well, such as:
|
||||||
* LayoutEditor http://www.layouteditor.net/
|
* [LayoutEditor](http://www.layouteditor.net/)
|
||||||
|
|
||||||
|
|
||||||
# Example to output/input .gds layout files from/to Cadence
|
# Example to output/input .gds layout files from/to Cadence
|
||||||
|
|
|
||||||
20
LICENSE
20
LICENSE
|
|
@ -1,23 +1,21 @@
|
||||||
BSD 3-Clause License
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2019 Regents of the University of California and The Board
|
Copyright (c) 2019, Regents of the University of California and The Board of Regents for the Oklahoma Agricultural and Mechanical College (acting for and on behalf of Oklahoma State University)
|
||||||
of Regents for the Oklahoma Agricultural and Mechanical College
|
|
||||||
(acting for and on behalf of Oklahoma State University)
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
list of conditions and the following disclaimer.
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
this list of conditions and the following disclaimer in the documentation
|
this list of conditions and the following disclaimer in the documentation
|
||||||
and/or other materials provided with the distribution.
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
* Neither the name of the copyright holder nor the names of its
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
contributors may be used to endorse or promote products derived from
|
contributors may be used to endorse or promote products derived from
|
||||||
this software without specific prior written permission.
|
this software without specific prior written permission.
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
|
|
||||||
57
README.md
57
README.md
|
|
@ -18,7 +18,7 @@ An open-source static random access memory (SRAM) compiler.
|
||||||
# What is OpenRAM?
|
# What is OpenRAM?
|
||||||
<img align="right" width="25%" src="images/SCMOS_16kb_sram.jpg">
|
<img align="right" width="25%" src="images/SCMOS_16kb_sram.jpg">
|
||||||
|
|
||||||
OpenRAM is an open-source Python framework to create the layout,
|
OpenRAM is an award winning open-source Python framework to create the layout,
|
||||||
netlists, timing and power models, placement and routing models, and
|
netlists, timing and power models, placement and routing models, and
|
||||||
other views necessary to use SRAMs in ASIC design. OpenRAM supports
|
other views necessary to use SRAMs in ASIC design. OpenRAM supports
|
||||||
integration in both commercial and open-source flows with both
|
integration in both commercial and open-source flows with both
|
||||||
|
|
@ -33,15 +33,6 @@ things that need to be fixed.
|
||||||
|
|
||||||
# Basic Setup
|
# Basic Setup
|
||||||
|
|
||||||
## Docker Image
|
|
||||||
|
|
||||||
We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
|
|
||||||
available that has all tools installed for the [SCMOS] process. It is
|
|
||||||
available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
|
|
||||||
Please see
|
|
||||||
[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
|
|
||||||
for information on how to use this docker image.
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
The OpenRAM compiler has very few dependencies:
|
The OpenRAM compiler has very few dependencies:
|
||||||
|
|
@ -88,6 +79,23 @@ You may get the entire [FreePDK45 PDK here][FreePDK45].
|
||||||
If you are using [SCMOS], you should install [Magic] and [Netgen].
|
If you are using [SCMOS], you should install [Magic] and [Netgen].
|
||||||
We have included the most recent SCN4M_SUBM design rules from [Qflow].
|
We have included the most recent SCN4M_SUBM design rules from [Qflow].
|
||||||
|
|
||||||
|
## Docker Image
|
||||||
|
|
||||||
|
We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
|
||||||
|
available that has all tools installed for the [SCMOS] process. It is
|
||||||
|
available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
|
||||||
|
Please see
|
||||||
|
[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
|
||||||
|
for information on how to use this docker image.
|
||||||
|
|
||||||
|
## Vagrant Image
|
||||||
|
|
||||||
|
We have a pre-configured Ubuntu [Vagrant](https://www.vagrantup.com/) image
|
||||||
|
available that has all tools installed for the [SCMOS] process.
|
||||||
|
Please see
|
||||||
|
[our README.md](https://github.com/VLSIDA/openram-vagrant-image/blob/master/README.md)
|
||||||
|
for information on how to use this image.
|
||||||
|
|
||||||
# Basic Usage
|
# Basic Usage
|
||||||
|
|
||||||
Once you have defined the environment, you can run OpenRAM from the command line
|
Once you have defined the environment, you can run OpenRAM from the command line
|
||||||
|
|
@ -104,12 +112,16 @@ num_words = 16
|
||||||
|
|
||||||
# Technology to use in $OPENRAM_TECH
|
# Technology to use in $OPENRAM_TECH
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
|
||||||
|
# You can use the technology nominal corner only
|
||||||
|
nominal_corner_only = True
|
||||||
|
# Or you can specify particular corners
|
||||||
# Process corners to characterize
|
# Process corners to characterize
|
||||||
process_corners = ["TT"]
|
# process_corners = ["SS", "TT", "FF"]
|
||||||
# Voltage corners to characterize
|
# Voltage corners to characterize
|
||||||
supply_voltages = [ 3.3 ]
|
# supply_voltages = [ 3.0, 3.3, 3.5 ]
|
||||||
# Temperature corners to characterize
|
# Temperature corners to characterize
|
||||||
temperatures = [ 25 ]
|
# temperatures = [ 0, 25 100]
|
||||||
|
|
||||||
# Output directory for the results
|
# Output directory for the results
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
|
|
@ -119,11 +131,6 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||||
# Disable analytical models for full characterization (WARNING: slow!)
|
# Disable analytical models for full characterization (WARNING: slow!)
|
||||||
# analytical_delay = False
|
# analytical_delay = False
|
||||||
|
|
||||||
# To force this to use magic and netgen for DRC/LVS/PEX
|
|
||||||
# Could be calibre for FreePDK45
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then run OpenRAM by executing:
|
You can then run OpenRAM by executing:
|
||||||
|
|
@ -188,7 +195,7 @@ specific technology (e.g., [FreePDK45]) should be a subdirectory
|
||||||
+ Report bugs by submitting [Github issues].
|
+ Report bugs by submitting [Github issues].
|
||||||
+ Develop new features (see [how to contribute](./CONTRIBUTING.md))
|
+ Develop new features (see [how to contribute](./CONTRIBUTING.md))
|
||||||
+ Submit code/fixes using a [Github pull request]
|
+ Submit code/fixes using a [Github pull request]
|
||||||
+ Follow our [project][Github projects].
|
+ Follow our [project][Github project].
|
||||||
+ Read and cite our [ICCAD paper][OpenRAMpaper]
|
+ Read and cite our [ICCAD paper][OpenRAMpaper]
|
||||||
|
|
||||||
# Further Help
|
# Further Help
|
||||||
|
|
@ -207,15 +214,7 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE).
|
||||||
|
|
||||||
- [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect.
|
- [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect.
|
||||||
- [James Stine] from [VLSIARCH] co-founded the project.
|
- [James Stine] from [VLSIARCH] co-founded the project.
|
||||||
- Hunter Nichols maintains and updates the timing characterization.
|
- Many students: Hunter Nichols, Michael Grimes, Jennifer Sowash, Yusu Wang, Joey Kunzler, Jesse Cirimelli-Low, Samira Ataei, Bin Wu, Brian Chen, Jeff Butera
|
||||||
- Michael Grimes created and maintains the multiport netlist code.
|
|
||||||
- Jennifer Sowash is creating the OpenRAM IP library.
|
|
||||||
- Jesse Cirimelli-Low created the datasheet generation.
|
|
||||||
- Samira Ataei created early multi-bank layouts and control logic.
|
|
||||||
- Bin Wu created early parameterized cells.
|
|
||||||
- Yusu Wang is porting parameterized cells to new technologies.
|
|
||||||
- Brian Chen created early prototypes of the timing characterizer.
|
|
||||||
- Jeff Butera created early prototypes of the bank layout.
|
|
||||||
|
|
||||||
If I forgot to add you, please let me know!
|
If I forgot to add you, please let me know!
|
||||||
|
|
||||||
|
|
@ -229,7 +228,7 @@ If I forgot to add you, please let me know!
|
||||||
|
|
||||||
[Github issues]: https://github.com/VLSIDA/OpenRAM/issues
|
[Github issues]: https://github.com/VLSIDA/OpenRAM/issues
|
||||||
[Github pull request]: https://github.com/VLSIDA/OpenRAM/pulls
|
[Github pull request]: https://github.com/VLSIDA/OpenRAM/pulls
|
||||||
[Github projects]: https://github.com/VLSIDA/OpenRAM/projects
|
[Github project]: https://github.com/VLSIDA/OpenRAM
|
||||||
|
|
||||||
[documentation]: https://docs.google.com/presentation/d/10InGB33N51I6oBHnqpU7_w9DXlx-qe9zdrlco2Yc5co/edit?usp=sharing
|
[documentation]: https://docs.google.com/presentation/d/10InGB33N51I6oBHnqpU7_w9DXlx-qe9zdrlco2Yc5co/edit?usp=sharing
|
||||||
[dev-group]: mailto:openram-dev-group@ucsc.edu
|
[dev-group]: mailto:openram-dev-group@ucsc.edu
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
theme: jekyll-theme-minimal
|
theme: jekyll-theme-dinky
|
||||||
|
|
@ -0,0 +1,398 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import collections
|
||||||
|
import debug
|
||||||
|
from tech import drc
|
||||||
|
from vector import vector
|
||||||
|
import design
|
||||||
|
|
||||||
|
|
||||||
|
class channel_net():
|
||||||
|
def __init__(self, net_name, pins, vertical):
|
||||||
|
self.name = net_name
|
||||||
|
self.pins = pins
|
||||||
|
self.vertical = vertical
|
||||||
|
|
||||||
|
# Keep track of the internval
|
||||||
|
if vertical:
|
||||||
|
self.min_value = min(i.by() for i in pins)
|
||||||
|
self.max_value = max(i.uy() for i in pins)
|
||||||
|
else:
|
||||||
|
self.min_value = min(i.lx() for i in pins)
|
||||||
|
self.max_value = max(i.rx() for i in pins)
|
||||||
|
|
||||||
|
# Keep track of the conflicts
|
||||||
|
self.conflicts = []
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.min_value < other.min_value
|
||||||
|
|
||||||
|
def vcg_pin_overlap(self, pin1, pin2, pitch):
|
||||||
|
""" Check for vertical or horizontal overlap of the two pins """
|
||||||
|
|
||||||
|
# FIXME: If the pins are not in a row, this may break.
|
||||||
|
# However, a top pin shouldn't overlap another top pin,
|
||||||
|
# for example, so the extra comparison *shouldn't* matter.
|
||||||
|
|
||||||
|
# Pin 1 must be in the "BOTTOM" set
|
||||||
|
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
|
||||||
|
|
||||||
|
# Pin 1 must be in the "LEFT" set
|
||||||
|
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
|
||||||
|
overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap)
|
||||||
|
return overlaps
|
||||||
|
|
||||||
|
def vcg_nets_overlap(self, other, pitch):
|
||||||
|
"""
|
||||||
|
Check all the pin pairs on two nets and return a pin
|
||||||
|
overlap if any pin overlaps.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for pin1 in self.pins:
|
||||||
|
for pin2 in other.pins:
|
||||||
|
if self.vcg_pin_overlap(pin1, pin2, pitch):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def hcg_nets_overlap(self, other):
|
||||||
|
"""
|
||||||
|
Check if the horizontal span of the two nets overlaps eachother.
|
||||||
|
"""
|
||||||
|
min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value
|
||||||
|
max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value
|
||||||
|
return min_overlap or max_overlap
|
||||||
|
|
||||||
|
|
||||||
|
class channel_route(design.design):
|
||||||
|
|
||||||
|
unique_id = 0
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
netlist,
|
||||||
|
offset,
|
||||||
|
layer_stack,
|
||||||
|
directions=None,
|
||||||
|
vertical=False):
|
||||||
|
"""
|
||||||
|
The net list is a list of the nets with each net being a list of pins
|
||||||
|
to be connected. The offset is the lower-left of where the
|
||||||
|
routing channel will start. This does NOT try to minimize the
|
||||||
|
number of tracks -- instead, it picks an order to avoid the
|
||||||
|
vertical conflicts between pins. The track size must be the number of
|
||||||
|
nets times the *nonpreferred* routing of the non-track layer pitch.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = "cr_{0}".format(channel_route.unique_id)
|
||||||
|
channel_route.unique_id += 1
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.netlist = netlist
|
||||||
|
self.offset = offset
|
||||||
|
self.layer_stack = layer_stack
|
||||||
|
self.directions = directions
|
||||||
|
self.vertical = vertical
|
||||||
|
|
||||||
|
if not directions or directions == "pref":
|
||||||
|
# Use the preferred layer directions
|
||||||
|
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||||
|
self.vertical_layer = layer_stack[0]
|
||||||
|
self.horizontal_layer = layer_stack[2]
|
||||||
|
else:
|
||||||
|
self.vertical_layer = layer_stack[2]
|
||||||
|
self.horizontal_layer = layer_stack[0]
|
||||||
|
elif directions == "nonpref":
|
||||||
|
# Use the preferred layer directions
|
||||||
|
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||||
|
self.vertical_layer = layer_stack[2]
|
||||||
|
self.horizontal_layer = layer_stack[0]
|
||||||
|
else:
|
||||||
|
self.vertical_layer = layer_stack[0]
|
||||||
|
self.horizontal_layer = layer_stack[2]
|
||||||
|
else:
|
||||||
|
# Use the layer directions specified to the router rather than
|
||||||
|
# the preferred directions
|
||||||
|
debug.check(directions[0] != directions[1], "Must have unique layer directions.")
|
||||||
|
if directions[0] == "V":
|
||||||
|
self.vertical_layer = layer_stack[0]
|
||||||
|
self.horizontal_layer = layer_stack[2]
|
||||||
|
else:
|
||||||
|
self.horizontal_layer = layer_stack[0]
|
||||||
|
self.vertical_layer = layer_stack[2]
|
||||||
|
|
||||||
|
layer_stuff = self.get_layer_pitch(self.vertical_layer)
|
||||||
|
(self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
|
||||||
|
|
||||||
|
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
|
||||||
|
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
|
||||||
|
|
||||||
|
self.route()
|
||||||
|
|
||||||
|
def remove_net_from_graph(self, pin, g):
|
||||||
|
"""
|
||||||
|
Remove the pin from the graph and all conflicts
|
||||||
|
"""
|
||||||
|
g.pop(pin, None)
|
||||||
|
|
||||||
|
# Remove the pin from all conflicts
|
||||||
|
# FIXME: This is O(n^2), so maybe optimize it.
|
||||||
|
for other_pin, conflicts in g.items():
|
||||||
|
if pin in conflicts:
|
||||||
|
g[other_pin].remove(pin)
|
||||||
|
return g
|
||||||
|
|
||||||
|
def route(self):
|
||||||
|
# Create names for the nets for the graphs
|
||||||
|
nets = []
|
||||||
|
index = 0
|
||||||
|
# print(self.netlist)
|
||||||
|
for pin_list in self.netlist:
|
||||||
|
nets.append(channel_net("n{}".format(index), pin_list, self.vertical))
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
# Create the (undirected) horizontal constraint graph
|
||||||
|
hcg = collections.OrderedDict()
|
||||||
|
for net1 in nets:
|
||||||
|
for net2 in nets:
|
||||||
|
if net1.name == net2.name:
|
||||||
|
continue
|
||||||
|
if net1.hcg_nets_overlap(net2):
|
||||||
|
try:
|
||||||
|
hcg[net1.name].add(net2.name)
|
||||||
|
except KeyError:
|
||||||
|
hcg[net1.name] = set([net2.name])
|
||||||
|
try:
|
||||||
|
hcg[net2.name].add(net1.name)
|
||||||
|
except KeyError:
|
||||||
|
hcg[net2.name] = set([net1.name])
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize the vertical conflict graph (vcg)
|
||||||
|
# and make a list of all pins
|
||||||
|
vcg = collections.OrderedDict()
|
||||||
|
|
||||||
|
# print("Nets:")
|
||||||
|
# for net_name in nets:
|
||||||
|
# print(net_name, [x.name for x in nets[net_name]])
|
||||||
|
|
||||||
|
# Find the vertical pin conflicts
|
||||||
|
# FIXME: O(n^2) but who cares for now
|
||||||
|
if self.vertical:
|
||||||
|
pitch = self.horizontal_nonpref_pitch
|
||||||
|
else:
|
||||||
|
pitch = self.vertical_nonpref_pitch
|
||||||
|
|
||||||
|
for net in nets:
|
||||||
|
vcg[net.name] = set()
|
||||||
|
|
||||||
|
for net1 in nets:
|
||||||
|
for net2 in nets:
|
||||||
|
# Skip yourself
|
||||||
|
if net1.name == net2.name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if net1.vcg_nets_overlap(net2, pitch):
|
||||||
|
vcg[net2.name].add(net1.name)
|
||||||
|
|
||||||
|
# Check if there are any cycles net1 <---> net2 in the VCG
|
||||||
|
|
||||||
|
|
||||||
|
# Some of the pins may be to the left/below the channel offset,
|
||||||
|
# so adjust if this is the case
|
||||||
|
min_value = min([n.min_value for n in nets])
|
||||||
|
if self.vertical:
|
||||||
|
real_channel_offset = vector(self.offset.x, min_value)
|
||||||
|
else:
|
||||||
|
real_channel_offset = vector(min_value, self.offset.y)
|
||||||
|
current_offset = real_channel_offset
|
||||||
|
|
||||||
|
# Sort nets by left edge value
|
||||||
|
nets.sort()
|
||||||
|
while len(nets) > 0:
|
||||||
|
|
||||||
|
current_offset_value = current_offset.y if self.vertical else current_offset.x
|
||||||
|
|
||||||
|
# from pprint import pformat
|
||||||
|
# print("VCG:\n", pformat(vcg))
|
||||||
|
# for name,net in vcg.items():
|
||||||
|
# print(name, net.min_value, net.max_value, net.conflicts)
|
||||||
|
# print(current_offset)
|
||||||
|
# get a route from conflict graph with empty fanout set
|
||||||
|
for net in nets:
|
||||||
|
# If it has no conflicts and the interval is to the right of the current offset in the track
|
||||||
|
if net.min_value >= current_offset_value and len(vcg[net.name]) == 0:
|
||||||
|
# print("Routing {}".format(net.name))
|
||||||
|
# Add the trunk routes from the bottom up for
|
||||||
|
# horizontal or the left to right for vertical
|
||||||
|
if self.vertical:
|
||||||
|
self.add_vertical_trunk_route(net.pins,
|
||||||
|
current_offset,
|
||||||
|
self.vertical_nonpref_pitch)
|
||||||
|
current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch)
|
||||||
|
else:
|
||||||
|
self.add_horizontal_trunk_route(net.pins,
|
||||||
|
current_offset,
|
||||||
|
self.horizontal_nonpref_pitch)
|
||||||
|
current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y)
|
||||||
|
|
||||||
|
# Remove the net from other constriants in the VCG
|
||||||
|
vcg = self.remove_net_from_graph(net.name, vcg)
|
||||||
|
nets.remove(net)
|
||||||
|
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# If we made a full pass and the offset didn't change...
|
||||||
|
current_offset_value = current_offset.y if self.vertical else current_offset.x
|
||||||
|
initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x
|
||||||
|
if current_offset_value == initial_offset_value:
|
||||||
|
# FIXME: We don't support cyclic VCGs right now.
|
||||||
|
debug.error("Cyclic VCG in channel router.", -1)
|
||||||
|
|
||||||
|
# Increment the track and reset the offset to the start (like a typewriter)
|
||||||
|
if self.vertical:
|
||||||
|
current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y)
|
||||||
|
else:
|
||||||
|
current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch)
|
||||||
|
|
||||||
|
# Return the size of the channel
|
||||||
|
if self.vertical:
|
||||||
|
self.width = 0
|
||||||
|
self.height = current_offset.y
|
||||||
|
return current_offset.y + self.vertical_nonpref_pitch - self.offset.y
|
||||||
|
else:
|
||||||
|
self.width = current_offset.x
|
||||||
|
self.height = 0
|
||||||
|
return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
|
||||||
|
|
||||||
|
def get_layer_pitch(self, layer):
|
||||||
|
""" Return the track pitch on a given layer """
|
||||||
|
try:
|
||||||
|
# FIXME: Using non-pref pitch here due to overlap bug in VCG constraints.
|
||||||
|
# It should just result in inefficient channel width but will work.
|
||||||
|
pitch = getattr(self, "{}_pitch".format(layer))
|
||||||
|
nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer))
|
||||||
|
space = getattr(self, "{}_space".format(layer))
|
||||||
|
except AttributeError:
|
||||||
|
debug.error("Cannot find layer pitch.", -1)
|
||||||
|
return (nonpref_pitch, pitch, pitch - space, space)
|
||||||
|
|
||||||
|
def add_horizontal_trunk_route(self,
|
||||||
|
pins,
|
||||||
|
trunk_offset,
|
||||||
|
pitch):
|
||||||
|
"""
|
||||||
|
Create a trunk route for all pins with
|
||||||
|
the trunk located at the given y offset.
|
||||||
|
"""
|
||||||
|
max_x = max([pin.center().x for pin in pins])
|
||||||
|
min_x = min([pin.center().x for pin in pins])
|
||||||
|
|
||||||
|
# if we are less than a pitch, just create a non-preferred layer jog
|
||||||
|
non_preferred_route = max_x - min_x <= pitch
|
||||||
|
|
||||||
|
if non_preferred_route:
|
||||||
|
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
||||||
|
# Add the horizontal trunk on the vertical layer!
|
||||||
|
self.add_path(self.vertical_layer,
|
||||||
|
[vector(min_x - half_layer_width, trunk_offset.y),
|
||||||
|
vector(max_x + half_layer_width, trunk_offset.y)])
|
||||||
|
|
||||||
|
# Route each pin to the trunk
|
||||||
|
for pin in pins:
|
||||||
|
if pin.cy() < trunk_offset.y:
|
||||||
|
pin_pos = pin.uc()
|
||||||
|
else:
|
||||||
|
pin_pos = pin.bc()
|
||||||
|
|
||||||
|
# No bend needed here
|
||||||
|
mid = vector(pin_pos.x, trunk_offset.y)
|
||||||
|
self.add_path(self.vertical_layer, [pin_pos, mid])
|
||||||
|
else:
|
||||||
|
# Add the horizontal trunk
|
||||||
|
self.add_path(self.horizontal_layer,
|
||||||
|
[vector(min_x, trunk_offset.y),
|
||||||
|
vector(max_x, trunk_offset.y)])
|
||||||
|
|
||||||
|
# Route each pin to the trunk
|
||||||
|
for pin in pins:
|
||||||
|
# Find the correct side of the pin
|
||||||
|
if pin.cy() < trunk_offset.y:
|
||||||
|
pin_pos = pin.uc()
|
||||||
|
else:
|
||||||
|
pin_pos = pin.bc()
|
||||||
|
mid = vector(pin_pos.x, trunk_offset.y)
|
||||||
|
self.add_path(self.vertical_layer, [pin_pos, mid])
|
||||||
|
if not non_preferred_route:
|
||||||
|
self.add_via_center(layers=self.layer_stack,
|
||||||
|
offset=mid,
|
||||||
|
directions=self.directions)
|
||||||
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.vertical_layer,
|
||||||
|
offset=pin_pos)
|
||||||
|
|
||||||
|
def add_vertical_trunk_route(self,
|
||||||
|
pins,
|
||||||
|
trunk_offset,
|
||||||
|
pitch):
|
||||||
|
"""
|
||||||
|
Create a trunk route for all pins with the
|
||||||
|
trunk located at the given x offset.
|
||||||
|
"""
|
||||||
|
max_y = max([pin.center().y for pin in pins])
|
||||||
|
min_y = min([pin.center().y for pin in pins])
|
||||||
|
|
||||||
|
# if we are less than a pitch, just create a non-preferred layer jog
|
||||||
|
non_preferred_route = max_y - min_y <= pitch
|
||||||
|
|
||||||
|
if non_preferred_route:
|
||||||
|
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
||||||
|
# Add the vertical trunk on the horizontal layer!
|
||||||
|
self.add_path(self.horizontal_layer,
|
||||||
|
[vector(trunk_offset.x, min_y - half_layer_width),
|
||||||
|
vector(trunk_offset.x, max_y + half_layer_width)])
|
||||||
|
|
||||||
|
# Route each pin to the trunk
|
||||||
|
for pin in pins:
|
||||||
|
# Find the correct side of the pin
|
||||||
|
if pin.cx() < trunk_offset.x:
|
||||||
|
pin_pos = pin.rc()
|
||||||
|
else:
|
||||||
|
pin_pos = pin.lc()
|
||||||
|
# No bend needed here
|
||||||
|
mid = vector(trunk_offset.x, pin_pos.y)
|
||||||
|
self.add_path(self.horizontal_layer, [pin_pos, mid])
|
||||||
|
else:
|
||||||
|
# Add the vertical trunk
|
||||||
|
self.add_path(self.vertical_layer,
|
||||||
|
[vector(trunk_offset.x, min_y),
|
||||||
|
vector(trunk_offset.x, max_y)])
|
||||||
|
|
||||||
|
# Route each pin to the trunk
|
||||||
|
for pin in pins:
|
||||||
|
# Find the correct side of the pin
|
||||||
|
if pin.cx() < trunk_offset.x:
|
||||||
|
pin_pos = pin.rc()
|
||||||
|
else:
|
||||||
|
pin_pos = pin.lc()
|
||||||
|
mid = vector(trunk_offset.x, pin_pos.y)
|
||||||
|
self.add_path(self.horizontal_layer, [pin_pos, mid])
|
||||||
|
if not non_preferred_route:
|
||||||
|
self.add_via_center(layers=self.layer_stack,
|
||||||
|
offset=mid,
|
||||||
|
directions=self.directions)
|
||||||
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.horizontal_layer,
|
||||||
|
offset=pin_pos)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
import hierarchy_design
|
import hierarchy_design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, layer
|
from tech import drc, layer
|
||||||
|
import tech
|
||||||
from vector import vector
|
from vector import vector
|
||||||
|
from sram_factory import factory
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class contact(hierarchy_design.hierarchy_design):
|
class contact(hierarchy_design.hierarchy_design):
|
||||||
|
|
@ -26,22 +29,42 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"),
|
def __init__(self, layer_stack, dimensions=(1, 1), directions=None,
|
||||||
implant_type=None, well_type=None, name=""):
|
implant_type=None, well_type=None, name=""):
|
||||||
# This will ignore the name parameter since
|
# This will ignore the name parameter since
|
||||||
# we can guarantee a unique name here
|
# we can guarantee a unique name here
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
self.add_comment("layers: {0}".format(layer_stack))
|
self.add_comment("layers: {0}".format(layer_stack))
|
||||||
self.add_comment("dimensions: {0}".format(dimensions))
|
self.add_comment("dimensions: {0}".format(dimensions))
|
||||||
if implant_type or well_type:
|
if implant_type or well_type:
|
||||||
self.add_comment("implant type: {}\n".format(implant_type))
|
self.add_comment("implant type: {}\n".format(implant_type))
|
||||||
self.add_comment("well_type: {}\n".format(well_type))
|
self.add_comment("well_type: {}\n".format(well_type))
|
||||||
|
|
||||||
|
self.is_well_contact = implant_type == well_type
|
||||||
|
|
||||||
|
# If we have a special tap layer, use it
|
||||||
self.layer_stack = layer_stack
|
self.layer_stack = layer_stack
|
||||||
self.dimensions = dimensions
|
self.dimensions = dimensions
|
||||||
self.directions = directions
|
|
||||||
|
# Non-preferred directions
|
||||||
|
if directions == "nonpref":
|
||||||
|
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
|
||||||
|
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
|
||||||
|
self.directions = (first_dir, second_dir)
|
||||||
|
# Preferred directions
|
||||||
|
elif directions == "pref":
|
||||||
|
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||||
|
tech.preferred_directions[layer_stack[2]])
|
||||||
|
# User directions
|
||||||
|
elif directions:
|
||||||
|
self.directions = directions
|
||||||
|
# Preferred directions
|
||||||
|
else:
|
||||||
|
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||||
|
tech.preferred_directions[layer_stack[2]])
|
||||||
self.offset = vector(0, 0)
|
self.offset = vector(0, 0)
|
||||||
self.implant_type = implant_type
|
self.implant_type = implant_type
|
||||||
self.well_type = well_type
|
self.well_type = well_type
|
||||||
|
|
@ -56,30 +79,39 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
self.create_contact_array()
|
self.create_contact_array()
|
||||||
self.create_first_layer_enclosure()
|
self.create_first_layer_enclosure()
|
||||||
self.create_second_layer_enclosure()
|
self.create_second_layer_enclosure()
|
||||||
|
self.create_nitride_cut_enclosure()
|
||||||
|
|
||||||
self.height = max(obj.offset.y + obj.height for obj in self.objs)
|
self.height = max(self.first_layer_position.y + self.first_layer_height,
|
||||||
self.width = max(obj.offset.x + obj.width for obj in self.objs)
|
self.second_layer_position.y + self.second_layer_height)
|
||||||
|
self.width = max(self.first_layer_position.x + self.first_layer_width,
|
||||||
|
self.second_layer_position.x + self.second_layer_width)
|
||||||
|
|
||||||
# Do not include the select layer in the height/width
|
# Do not include the select layer in the height/width
|
||||||
if self.implant_type and self.well_type:
|
if self.implant_type and self.well_type:
|
||||||
self.create_implant_well_enclosures()
|
self.create_implant_well_enclosures()
|
||||||
elif self.implant_type or self.well_type:
|
elif self.implant_type or self.well_type:
|
||||||
debug.error(-1, "Must define both implant and well type or none at all.")
|
debug.error(-1,
|
||||||
|
"Must define both implant and well type or none.")
|
||||||
|
|
||||||
def setup_layers(self):
|
def setup_layers(self):
|
||||||
""" Locally assign the layer names. """
|
""" Locally assign the layer names. """
|
||||||
|
|
||||||
(first_layer, via_layer, second_layer) = self.layer_stack
|
(first_layer, via_layer, second_layer) = self.layer_stack
|
||||||
self.first_layer_name = first_layer
|
self.first_layer_name = first_layer
|
||||||
self.via_layer_name = via_layer
|
|
||||||
# Some technologies have a separate active
|
|
||||||
# contact from the poly contact
|
|
||||||
# We will use contact for DRC, but active_contact for output
|
|
||||||
if first_layer == "active" or second_layer == "active":
|
|
||||||
self.via_layer_name_expanded = "active_" + via_layer
|
|
||||||
else:
|
|
||||||
self.via_layer_name_expanded = via_layer
|
|
||||||
self.second_layer_name = second_layer
|
self.second_layer_name = second_layer
|
||||||
|
|
||||||
|
# Contacts will have unique per first layer
|
||||||
|
if via_layer in tech.layer:
|
||||||
|
self.via_layer_name = via_layer
|
||||||
|
elif via_layer == "contact":
|
||||||
|
if first_layer in ("active", "poly"):
|
||||||
|
self.via_layer_name = first_layer + "_" + via_layer
|
||||||
|
elif second_layer in ("active", "poly"):
|
||||||
|
self.via_layer_name = second_layer + "_" + via_layer
|
||||||
|
else:
|
||||||
|
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
def setup_layout_constants(self):
|
||||||
""" Determine the design rules for the enclosure layers """
|
""" Determine the design rules for the enclosure layers """
|
||||||
|
|
@ -95,70 +127,105 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
# The extend rule applies to asymmetric enclosures in one direction.
|
# The extend rule applies to asymmetric enclosures in one direction.
|
||||||
# The enclosure rule applies to symmetric enclosure component.
|
# The enclosure rule applies to symmetric enclosure component.
|
||||||
|
|
||||||
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||||
first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name))
|
self.first_layer_enclosure = drc("{0}_enclose_{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))
|
# If there's a different rule for active
|
||||||
|
# FIXME: Make this more elegant
|
||||||
|
if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys():
|
||||||
|
self.first_layer_extend = drc("tap_extend_contact")
|
||||||
|
else:
|
||||||
|
self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||||
|
|
||||||
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||||
second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name))
|
self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||||
second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||||
|
|
||||||
# In some technologies, the minimum width may be larger
|
# In some technologies, the minimum width may be larger
|
||||||
# than the overlap requirement around the via, so
|
# than the overlap requirement around the via, so
|
||||||
# check this for each dimension.
|
# check this for each dimension.
|
||||||
if self.directions[0] == "V":
|
if self.directions[0] == "V":
|
||||||
self.first_layer_horizontal_enclosure = max(first_layer_enclosure,
|
self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure,
|
||||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||||
self.first_layer_vertical_enclosure = max(first_layer_extend,
|
self.first_layer_vertical_enclosure = max(self.first_layer_extend,
|
||||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||||
elif self.directions[0] == "H":
|
elif self.directions[0] == "H":
|
||||||
self.first_layer_horizontal_enclosure = max(first_layer_extend,
|
self.first_layer_horizontal_enclosure = max(self.first_layer_extend,
|
||||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||||
self.first_layer_vertical_enclosure = max(first_layer_enclosure,
|
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
|
||||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid first layer direction.", -1)
|
debug.error("Invalid first layer direction: ".format(self.directions[0]), -1)
|
||||||
|
|
||||||
# In some technologies, the minimum width may be larger than the overlap requirement around the via, so
|
# In some technologies, the minimum width may be larger
|
||||||
|
# than the overlap requirement around the via, so
|
||||||
# check this for each dimension.
|
# check this for each dimension.
|
||||||
if self.directions[1] == "V":
|
if self.directions[1] == "V":
|
||||||
self.second_layer_horizontal_enclosure = max(second_layer_enclosure,
|
self.second_layer_horizontal_enclosure = max(self.second_layer_enclosure,
|
||||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||||
self.second_layer_vertical_enclosure = max(second_layer_extend,
|
self.second_layer_vertical_enclosure = max(self.second_layer_extend,
|
||||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||||
elif self.directions[1] == "H":
|
elif self.directions[1] == "H":
|
||||||
self.second_layer_horizontal_enclosure = max(second_layer_extend,
|
self.second_layer_horizontal_enclosure = max(self.second_layer_extend,
|
||||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||||
self.second_layer_vertical_enclosure = max(second_layer_enclosure,
|
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
|
||||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid second layer direction.", -1)
|
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
|
||||||
|
|
||||||
def create_contact_array(self):
|
def create_contact_array(self):
|
||||||
""" Create the contact array at the origin"""
|
""" Create the contact array at the origin"""
|
||||||
# offset for the via array
|
# offset for the via array
|
||||||
self.via_layer_position = vector(
|
self.via_layer_position = vector(
|
||||||
max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure),
|
max(self.first_layer_horizontal_enclosure,
|
||||||
max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure))
|
self.second_layer_horizontal_enclosure),
|
||||||
|
max(self.first_layer_vertical_enclosure,
|
||||||
|
self.second_layer_vertical_enclosure))
|
||||||
|
|
||||||
for i in range(self.dimensions[1]):
|
for i in range(self.dimensions[1]):
|
||||||
offset = self.via_layer_position + vector(0, self.contact_pitch * i)
|
offset = self.via_layer_position + vector(0,
|
||||||
|
self.contact_pitch * i)
|
||||||
for j in range(self.dimensions[0]):
|
for j in range(self.dimensions[0]):
|
||||||
self.add_rect(layer=self.via_layer_name_expanded,
|
self.add_rect(layer=self.via_layer_name,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
width=self.contact_width,
|
width=self.contact_width,
|
||||||
height=self.contact_width)
|
height=self.contact_width)
|
||||||
offset = offset + vector(self.contact_pitch, 0)
|
offset = offset + vector(self.contact_pitch, 0)
|
||||||
|
|
||||||
|
def create_nitride_cut_enclosure(self):
|
||||||
|
""" Special layer that encloses poly contacts in some processes """
|
||||||
|
# Check if there is a special poly nitride cut layer
|
||||||
|
if "npc" not in tech.layer:
|
||||||
|
return
|
||||||
|
|
||||||
|
npc_enclose_poly = drc("npc_enclose_poly")
|
||||||
|
npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly)
|
||||||
|
# Only add for poly layers
|
||||||
|
if self.first_layer_name == "poly":
|
||||||
|
self.add_rect(layer="npc",
|
||||||
|
offset=self.first_layer_position - npc_enclose_offset,
|
||||||
|
width=self.first_layer_width + 2 * npc_enclose_poly,
|
||||||
|
height=self.first_layer_height + 2 * npc_enclose_poly)
|
||||||
|
elif self.second_layer_name == "poly":
|
||||||
|
self.add_rect(layer="npc",
|
||||||
|
offset=self.second_layer_position - npc_enclose_offset,
|
||||||
|
width=self.second_layer_width + 2 * npc_enclose_poly,
|
||||||
|
height=self.second_layer_height + 2 * npc_enclose_poly)
|
||||||
|
|
||||||
def create_first_layer_enclosure(self):
|
def create_first_layer_enclosure(self):
|
||||||
# this is if the first and second layers are different
|
# this is if the first and second layers are different
|
||||||
self.first_layer_position = vector(
|
self.first_layer_position = vector(
|
||||||
max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0),
|
max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0),
|
||||||
max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0))
|
max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0))
|
||||||
|
|
||||||
self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure
|
self.first_layer_width = max(self.contact_array_width + 2 * self.first_layer_horizontal_enclosure,
|
||||||
self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure
|
self.first_layer_minwidth)
|
||||||
self.add_rect(layer=self.first_layer_name,
|
self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure,
|
||||||
|
self.first_layer_minwidth)
|
||||||
|
if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer:
|
||||||
|
first_layer_name = "tap"
|
||||||
|
else:
|
||||||
|
first_layer_name = self.first_layer_name
|
||||||
|
self.add_rect(layer=first_layer_name,
|
||||||
offset=self.first_layer_position,
|
offset=self.first_layer_position,
|
||||||
width=self.first_layer_width,
|
width=self.first_layer_width,
|
||||||
height=self.first_layer_height)
|
height=self.first_layer_height)
|
||||||
|
|
@ -169,57 +236,72 @@ class contact(hierarchy_design.hierarchy_design):
|
||||||
max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0),
|
max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0),
|
||||||
max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0))
|
max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0))
|
||||||
|
|
||||||
self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure
|
self.second_layer_width = max(self.contact_array_width + 2 * self.second_layer_horizontal_enclosure,
|
||||||
self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure
|
self.second_layer_minwidth)
|
||||||
|
self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure,
|
||||||
|
self.second_layer_minwidth)
|
||||||
self.add_rect(layer=self.second_layer_name,
|
self.add_rect(layer=self.second_layer_name,
|
||||||
offset=self.second_layer_position,
|
offset=self.second_layer_position,
|
||||||
width=self.second_layer_width,
|
width=self.second_layer_width,
|
||||||
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_enclose_active")] * 2
|
||||||
implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active")
|
implant_width = self.first_layer_width + 2 * drc("implant_enclose_active")
|
||||||
implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active")
|
implant_height = self.first_layer_height + 2 * drc("implant_enclose_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_width = self.first_layer_width + 2 * drc("well_enclosure_active")
|
# Optionally implant well if layer exists
|
||||||
well_height = self.first_layer_height + 2 * drc("well_enclosure_active")
|
well_layer = "{}well".format(self.well_type)
|
||||||
self.add_rect(layer="{}well".format(self.well_type),
|
if well_layer in tech.layer:
|
||||||
offset=well_position,
|
well_width_rule = drc("minwidth_" + well_layer)
|
||||||
width=well_width,
|
self.well_enclose_active = drc(well_layer + "_enclose_active")
|
||||||
height=well_height)
|
self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active,
|
||||||
|
well_width_rule)
|
||||||
|
self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active,
|
||||||
|
well_width_rule)
|
||||||
|
center_pos = vector(0.5*self.width, 0.5*self.height)
|
||||||
|
well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height)
|
||||||
|
self.add_rect(layer=well_layer,
|
||||||
|
offset=well_position,
|
||||||
|
width=self.well_width,
|
||||||
|
height=self.well_height)
|
||||||
|
|
||||||
def analytical_power(self, corner, load):
|
def analytical_power(self, corner, load):
|
||||||
""" Get total power of a module """
|
""" Get total power of a module """
|
||||||
return self.return_power()
|
return self.return_power()
|
||||||
|
|
||||||
|
|
||||||
from sram_factory import factory
|
# Set up a static for each layer to be used for measurements
|
||||||
|
for layer_stack in tech.layer_stacks:
|
||||||
|
(layer1, via, layer2) = layer_stack
|
||||||
|
cont = factory.create(module_type="contact",
|
||||||
|
layer_stack=layer_stack)
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
# Also create a contact that is just the first layer
|
||||||
|
if layer1 == "poly" or layer1 == "active":
|
||||||
|
setattr(module, layer1 + "_contact", cont)
|
||||||
|
else:
|
||||||
|
setattr(module, layer1 + "_via", cont)
|
||||||
|
|
||||||
|
# Set up a static for each well contact for measurements
|
||||||
|
if "nwell" in tech.layer:
|
||||||
|
cont = factory.create(module_type="contact",
|
||||||
|
layer_stack=tech.active_stack,
|
||||||
|
implant_type="n",
|
||||||
|
well_type="n")
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
setattr(module, "nwell_contact", cont)
|
||||||
|
|
||||||
|
if "pwell" in tech.layer:
|
||||||
|
cont = factory.create(module_type="contact",
|
||||||
|
layer_stack=tech.active_stack,
|
||||||
|
implant_type="p",
|
||||||
|
well_type="p")
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
setattr(module, "pwell_contact", cont)
|
||||||
|
|
||||||
# This is not instantiated and used for calculations only.
|
|
||||||
# These are static 1x1 contacts to reuse in all the design modules.
|
|
||||||
well = factory.create(module_type="contact",
|
|
||||||
layer_stack=("active", "contact", "metal1"),
|
|
||||||
directions=("H", "V"))
|
|
||||||
active = factory.create(module_type="contact",
|
|
||||||
layer_stack=("active", "contact", "metal1"),
|
|
||||||
directions=("H", "V"))
|
|
||||||
poly = factory.create(module_type="contact",
|
|
||||||
layer_stack=("poly", "contact", "metal1"),
|
|
||||||
directions=("V", "H"))
|
|
||||||
m1m2 = factory.create(module_type="contact",
|
|
||||||
layer_stack=("metal1", "via1", "metal2"),
|
|
||||||
directions=("H", "V"))
|
|
||||||
m2m3 = factory.create(module_type="contact",
|
|
||||||
layer_stack=("metal2", "via2", "metal3"),
|
|
||||||
directions=("V", "H"))
|
|
||||||
if "metal4" in layer.keys():
|
|
||||||
m3m4 = factory.create(module_type="contact",
|
|
||||||
layer_stack=("metal3", "via3", "metal4"),
|
|
||||||
directions=("H", "V"))
|
|
||||||
else:
|
|
||||||
m3m4 = None
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2020 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
class _pins:
|
||||||
|
def __init__(self, pin_dict):
|
||||||
|
# make the pins elements of the class to allow "." access.
|
||||||
|
# For example: props.bitcell.cell_6t.pin.bl = "foobar"
|
||||||
|
for k,v in pin_dict.items():
|
||||||
|
self.__dict__[k] = v
|
||||||
|
|
||||||
|
class _cell:
|
||||||
|
def __init__(self, pin_dict):
|
||||||
|
pin_dict.update(self._default_power_pins())
|
||||||
|
self._pins = _pins(pin_dict)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pin(self):
|
||||||
|
return self._pins
|
||||||
|
|
||||||
|
def _default_power_pins(self):
|
||||||
|
return { 'vdd' : 'vdd', 'gnd' : 'gnd' }
|
||||||
|
|
||||||
|
class _mirror_axis:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
class _bitcell:
|
||||||
|
def __init__(self, mirror, split_wl, cell_6t, cell_1rw1r, cell_1w1r):
|
||||||
|
self.mirror = mirror
|
||||||
|
self.split_wl = split_wl
|
||||||
|
self._6t = cell_6t
|
||||||
|
self._1rw1r = cell_1rw1r
|
||||||
|
self._1w1r = cell_1w1r
|
||||||
|
|
||||||
|
def _default():
|
||||||
|
axis = _mirror_axis(True, False)
|
||||||
|
cell_6t = _cell({'bl' : 'bl',
|
||||||
|
'br' : 'br',
|
||||||
|
'wl' : 'wl'})
|
||||||
|
|
||||||
|
cell_1rw1r = _cell({'bl0' : 'bl0',
|
||||||
|
'br0' : 'br0',
|
||||||
|
'bl1' : 'bl1',
|
||||||
|
'br1' : 'br1',
|
||||||
|
'wl0' : 'wl0',
|
||||||
|
'wl1' : 'wl1'})
|
||||||
|
cell_1w1r = _cell({'bl0' : 'bl0',
|
||||||
|
'br0' : 'br0',
|
||||||
|
'bl1' : 'bl1',
|
||||||
|
'br1' : 'br1',
|
||||||
|
'wl0' : 'wl0',
|
||||||
|
'wl1' : 'wl1'})
|
||||||
|
return _bitcell(cell_6t=cell_6t,
|
||||||
|
cell_1rw1r=cell_1rw1r,
|
||||||
|
cell_1w1r=cell_1w1r,
|
||||||
|
split_wl = False,
|
||||||
|
mirror=axis)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cell_6t(self):
|
||||||
|
return self._6t
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cell_1rw1r(self):
|
||||||
|
return self._1rw1r
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cell_1w1r(self):
|
||||||
|
return self._1w1r
|
||||||
|
|
||||||
|
|
||||||
|
class _dff:
|
||||||
|
def __init__(self, use_custom_ports, custom_port_list, custom_type_list, clk_pin):
|
||||||
|
self.use_custom_ports = use_custom_ports
|
||||||
|
self.custom_port_list = custom_port_list
|
||||||
|
self.custom_type_list = custom_type_list
|
||||||
|
self.clk_pin = clk_pin
|
||||||
|
|
||||||
|
class _dff_buff:
|
||||||
|
def __init__(self, use_custom_ports, custom_buff_ports, add_body_contacts):
|
||||||
|
self.use_custom_ports = use_custom_ports
|
||||||
|
self.buf_ports = custom_buff_ports
|
||||||
|
self.add_body_contacts = add_body_contacts
|
||||||
|
|
||||||
|
class _dff_buff_array:
|
||||||
|
def __init__(self, use_custom_ports, add_body_contacts):
|
||||||
|
self.use_custom_ports = use_custom_ports
|
||||||
|
self.add_body_contacts = add_body_contacts
|
||||||
|
|
||||||
|
class cell_properties():
|
||||||
|
"""
|
||||||
|
This contains meta information about the custom designed cells. For
|
||||||
|
instance, pin names, or the axis on which they need to be mirrored. These
|
||||||
|
can be overriden in the tech.py file.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.names = {}
|
||||||
|
|
||||||
|
self._bitcell = _bitcell._default()
|
||||||
|
|
||||||
|
self._dff = _dff(use_custom_ports = False,
|
||||||
|
custom_port_list = ["D", "Q", "clk", "vdd", "gnd"],
|
||||||
|
custom_type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"],
|
||||||
|
clk_pin= "clk")
|
||||||
|
|
||||||
|
self._dff_buff = _dff_buff(use_custom_ports = False,
|
||||||
|
custom_buff_ports = ["D", "qint", "clk", "vdd", "gnd"],
|
||||||
|
add_body_contacts = False)
|
||||||
|
|
||||||
|
self._dff_buff_array = _dff_buff_array(use_custom_ports = False,
|
||||||
|
add_body_contacts = False)
|
||||||
|
|
||||||
|
self._write_driver = _cell({'din': 'din',
|
||||||
|
'bl' : 'bl',
|
||||||
|
'br' : 'br',
|
||||||
|
'en' : 'en'})
|
||||||
|
self._sense_amp = _cell({'bl' : 'bl',
|
||||||
|
'br' : 'br',
|
||||||
|
'dout' : 'dout',
|
||||||
|
'en' : 'en'})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bitcell(self):
|
||||||
|
return self._bitcell
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dff(self):
|
||||||
|
return self._dff
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dff_buff(self):
|
||||||
|
return self._dff_buff
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dff_buff_array(self):
|
||||||
|
return self._dff_buff_array
|
||||||
|
|
||||||
|
@property
|
||||||
|
def write_driver(self):
|
||||||
|
return self._write_driver
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sense_amp(self):
|
||||||
|
return self._sense_amp
|
||||||
|
|
@ -6,14 +6,16 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from hierarchy_design import hierarchy_design
|
from hierarchy_design import hierarchy_design
|
||||||
|
from utils import round_to_grid
|
||||||
import contact
|
import contact
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class design(hierarchy_design):
|
class design(hierarchy_design):
|
||||||
"""
|
"""
|
||||||
This is the same as the hierarchy_design class except it contains
|
This is the same as the hierarchy_design class except it contains
|
||||||
some DRC constants and analytical models for other modules to reuse.
|
some DRC/layer constants and analytical models for other modules to reuse.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -21,42 +23,195 @@ class design(hierarchy_design):
|
||||||
hierarchy_design.__init__(self, name)
|
hierarchy_design.__init__(self, name)
|
||||||
|
|
||||||
self.setup_drc_constants()
|
self.setup_drc_constants()
|
||||||
|
self.setup_layer_constants()
|
||||||
self.setup_multiport_constants()
|
self.setup_multiport_constants()
|
||||||
|
|
||||||
from tech import layer
|
def setup_layer_constants(self):
|
||||||
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)
|
These are some layer constants used
|
||||||
if "metal4" in layer:
|
in many places in the compiler.
|
||||||
self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space)
|
"""
|
||||||
|
|
||||||
|
from tech import layer_indices
|
||||||
|
import tech
|
||||||
|
for layer in layer_indices:
|
||||||
|
key = "{}_stack".format(layer)
|
||||||
|
|
||||||
|
# Set the stack as a local helper
|
||||||
|
try:
|
||||||
|
layer_stack = getattr(tech, key)
|
||||||
|
setattr(self, key, layer_stack)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Skip computing the pitch for active
|
||||||
|
if layer == "active":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add the pitch
|
||||||
|
setattr(self,
|
||||||
|
"{}_pitch".format(layer),
|
||||||
|
self.compute_pitch(layer, True))
|
||||||
|
|
||||||
|
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
||||||
|
setattr(self,
|
||||||
|
"{}_nonpref_pitch".format(layer),
|
||||||
|
self.compute_pitch(layer, False))
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from tech import preferred_directions
|
||||||
|
print(preferred_directions)
|
||||||
|
from tech import layer, layer_indices
|
||||||
|
for name in layer_indices:
|
||||||
|
if name == "active":
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
print("{0} width {1} space {2}".format(name,
|
||||||
|
getattr(self, "{}_width".format(name)),
|
||||||
|
getattr(self, "{}_space".format(name))))
|
||||||
|
|
||||||
|
print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)),
|
||||||
|
getattr(self, "{}_nonpref_pitch".format(name))))
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
import sys
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def compute_pitch(self, layer, preferred=True):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is the preferred direction pitch
|
||||||
|
i.e. we take the minimum or maximum contact dimension
|
||||||
|
"""
|
||||||
|
# Find the layer stacks this is used in
|
||||||
|
from tech import layer_stacks
|
||||||
|
pitches = []
|
||||||
|
for stack in layer_stacks:
|
||||||
|
# Compute the pitch with both vias above and below (if they exist)
|
||||||
|
if stack[0] == layer:
|
||||||
|
pitches.append(self.compute_layer_pitch(stack, preferred))
|
||||||
|
if stack[2] == layer:
|
||||||
|
pitches.append(self.compute_layer_pitch(stack[::-1], True))
|
||||||
|
|
||||||
|
return max(pitches)
|
||||||
|
|
||||||
|
def compute_layer_pitch(self, layer_stack, preferred):
|
||||||
|
|
||||||
|
(layer1, via, layer2) = layer_stack
|
||||||
|
try:
|
||||||
|
if layer1 == "poly" or layer1 == "active":
|
||||||
|
contact1 = getattr(contact, layer1 + "_contact")
|
||||||
|
else:
|
||||||
|
contact1 = getattr(contact, layer1 + "_via")
|
||||||
|
except AttributeError:
|
||||||
|
contact1 = getattr(contact, layer2 + "_via")
|
||||||
|
|
||||||
|
if preferred:
|
||||||
|
if self.get_preferred_direction(layer1) == "V":
|
||||||
|
contact_width = contact1.first_layer_width
|
||||||
|
else:
|
||||||
|
contact_width = contact1.first_layer_height
|
||||||
else:
|
else:
|
||||||
self.m3_pitch = self.m2_pitch
|
if self.get_preferred_direction(layer1) == "V":
|
||||||
|
contact_width = contact1.first_layer_height
|
||||||
|
else:
|
||||||
|
contact_width = contact1.first_layer_width
|
||||||
|
layer_space = getattr(self, layer1 + "_space")
|
||||||
|
|
||||||
|
#print(layer_stack)
|
||||||
|
#print(contact1)
|
||||||
|
pitch = contact_width + layer_space
|
||||||
|
|
||||||
|
return round_to_grid(pitch)
|
||||||
|
|
||||||
def setup_drc_constants(self):
|
def setup_drc_constants(self):
|
||||||
""" These are some DRC constants used in many places in the compiler."""
|
"""
|
||||||
from tech import drc, layer
|
These are some DRC constants used in many places
|
||||||
self.well_width = drc("minwidth_well")
|
in the compiler.
|
||||||
self.poly_width = drc("minwidth_poly")
|
"""
|
||||||
self.poly_space = drc("poly_to_poly")
|
# Make some local rules for convenience
|
||||||
self.m1_width = drc("minwidth_metal1")
|
from tech import drc
|
||||||
self.m1_space = drc("metal1_to_metal1")
|
for rule in drc.keys():
|
||||||
self.m2_width = drc("minwidth_metal2")
|
# Single layer width rules
|
||||||
self.m2_space = drc("metal2_to_metal2")
|
match = re.search(r"minwidth_(.*)", rule)
|
||||||
self.m3_width = drc("minwidth_metal3")
|
if match:
|
||||||
self.m3_space = drc("metal3_to_metal3")
|
if match.group(1) == "active_contact":
|
||||||
if "metal4" in layer:
|
setattr(self, "contact_width", drc(match.group(0)))
|
||||||
self.m4_width = drc("minwidth_metal4")
|
else:
|
||||||
self.m4_space = drc("metal4_to_metal4")
|
setattr(self, match.group(1) + "_width", drc(match.group(0)))
|
||||||
self.active_width = drc("minwidth_active")
|
|
||||||
self.active_space = drc("active_to_body_active")
|
|
||||||
self.contact_width = drc("minwidth_contact")
|
|
||||||
|
|
||||||
self.poly_to_active = drc("poly_to_active")
|
# Single layer area rules
|
||||||
self.poly_extend_active = drc("poly_extend_active")
|
match = re.search(r"minarea_(.*)", rule)
|
||||||
self.poly_to_polycontact = drc("poly_to_polycontact")
|
if match:
|
||||||
self.contact_to_gate = drc("contact_to_gate")
|
setattr(self, match.group(0), drc(match.group(0)))
|
||||||
self.well_enclose_active = drc("well_enclosure_active")
|
|
||||||
self.implant_enclose_active = drc("implant_enclosure_active")
|
# Single layer spacing rules
|
||||||
self.implant_space = drc("implant_to_implant")
|
match = re.search(r"(.*)_to_(.*)", rule)
|
||||||
|
if match and match.group(1) == match.group(2):
|
||||||
|
setattr(self, match.group(1) + "_space", drc(match.group(0)))
|
||||||
|
elif match and match.group(1) != match.group(2):
|
||||||
|
if match.group(2) == "poly_active":
|
||||||
|
setattr(self, match.group(1) + "_to_contact",
|
||||||
|
drc(match.group(0)))
|
||||||
|
else:
|
||||||
|
setattr(self, match.group(0), drc(match.group(0)))
|
||||||
|
|
||||||
|
match = re.search(r"(.*)_enclose_(.*)", rule)
|
||||||
|
if match:
|
||||||
|
setattr(self, match.group(0), drc(match.group(0)))
|
||||||
|
|
||||||
|
match = re.search(r"(.*)_extend_(.*)", rule)
|
||||||
|
if match:
|
||||||
|
setattr(self, match.group(0), drc(match.group(0)))
|
||||||
|
|
||||||
|
# Create the maximum well extend active that gets used
|
||||||
|
# by cells to extend the wells for interaction with other cells
|
||||||
|
from tech import layer
|
||||||
|
self.well_extend_active = 0
|
||||||
|
if "nwell" in layer:
|
||||||
|
self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active)
|
||||||
|
if "pwell" in layer:
|
||||||
|
self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active)
|
||||||
|
|
||||||
|
# The active offset is due to the well extension
|
||||||
|
if "pwell" in layer:
|
||||||
|
self.pwell_enclose_active = drc("pwell_enclose_active")
|
||||||
|
else:
|
||||||
|
self.pwell_enclose_active = 0
|
||||||
|
if "nwell" in layer:
|
||||||
|
self.nwell_enclose_active = drc("nwell_enclose_active")
|
||||||
|
else:
|
||||||
|
self.nwell_enclose_active = 0
|
||||||
|
# Use the max of either so that the poly gates will align properly
|
||||||
|
self.well_enclose_active = max(self.pwell_enclose_active,
|
||||||
|
self.nwell_enclose_active,
|
||||||
|
self.active_space)
|
||||||
|
|
||||||
|
# These are for debugging previous manual rules
|
||||||
|
if False:
|
||||||
|
print("poly_width", self.poly_width)
|
||||||
|
print("poly_space", self.poly_space)
|
||||||
|
print("m1_width", self.m1_width)
|
||||||
|
print("m1_space", self.m1_space)
|
||||||
|
print("m2_width", self.m2_width)
|
||||||
|
print("m2_space", self.m2_space)
|
||||||
|
print("m3_width", self.m3_width)
|
||||||
|
print("m3_space", self.m3_space)
|
||||||
|
print("m4_width", self.m4_width)
|
||||||
|
print("m4_space", self.m4_space)
|
||||||
|
print("active_width", self.active_width)
|
||||||
|
print("active_space", self.active_space)
|
||||||
|
print("contact_width", self.contact_width)
|
||||||
|
print("poly_to_active", self.poly_to_active)
|
||||||
|
print("poly_extend_active", self.poly_extend_active)
|
||||||
|
print("poly_to_contact", self.poly_to_contact)
|
||||||
|
print("active_contact_to_gate", self.active_contact_to_gate)
|
||||||
|
print("poly_contact_to_gate", self.poly_contact_to_gate)
|
||||||
|
print("well_enclose_active", self.well_enclose_active)
|
||||||
|
print("implant_enclose_active", self.implant_enclose_active)
|
||||||
|
print("implant_space", self.implant_space)
|
||||||
|
import sys
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def setup_multiport_constants(self):
|
def setup_multiport_constants(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
|
class drc_error(Exception):
|
||||||
|
"""Exception raised for DRC errors.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
expression -- input expression in which the error occurred
|
||||||
|
message -- explanation of the error
|
||||||
|
"""
|
||||||
|
|
||||||
|
# def __init__(self, expression, message):
|
||||||
|
# self.expression = expression
|
||||||
|
# self.message = message
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
|
@ -53,7 +53,7 @@ class geometry:
|
||||||
y = item[0] * math.sin(angle) + item[1] * mirr * math.cos(angle) + offset[1]
|
y = item[0] * math.sin(angle) + item[1] * mirr * math.cos(angle) + offset[1]
|
||||||
coordinate += [[x, y]]
|
coordinate += [[x, y]]
|
||||||
return coordinate
|
return coordinate
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -66,14 +66,19 @@ class geometry:
|
||||||
def update_boundary(self):
|
def update_boundary(self):
|
||||||
""" Update the boundary with a new placement. """
|
""" Update the boundary with a new placement. """
|
||||||
self.compute_boundary(self.offset, self.mirror, self.rotate)
|
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.
|
"""
|
||||||
We must then re-find the ll and ur. The master is the cell instance. """
|
Transform with offset, mirror and rotation to get the absolute pin location.
|
||||||
|
We must then re-find the ll and ur. The master is the cell instance.
|
||||||
|
"""
|
||||||
if OPTS.netlist_only:
|
if OPTS.netlist_only:
|
||||||
|
self.boundary = [vector(0, 0), vector(0, 0)]
|
||||||
return
|
return
|
||||||
|
|
||||||
(ll, ur) = [vector(0, 0), vector(self.width, self.height)]
|
(ll, ur) = [vector(0, 0), vector(self.width, self.height)]
|
||||||
|
|
||||||
|
# Mirroring is performed before rotation
|
||||||
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)
|
||||||
|
|
@ -83,8 +88,14 @@ class geometry:
|
||||||
elif mirror == "XY":
|
elif mirror == "XY":
|
||||||
ll = ll.scale(-1, -1)
|
ll = ll.scale(-1, -1)
|
||||||
ur = ur.scale(-1, -1)
|
ur = ur.scale(-1, -1)
|
||||||
|
elif mirror == "" or mirror == "R0":
|
||||||
if rotate == 90:
|
pass
|
||||||
|
else:
|
||||||
|
debug.error("Invalid mirroring: {}".format(mirror), -1)
|
||||||
|
|
||||||
|
if rotate == 0:
|
||||||
|
pass
|
||||||
|
elif rotate == 90:
|
||||||
ll = ll.rotate_scale(-1, 1)
|
ll = ll.rotate_scale(-1, 1)
|
||||||
ur = ur.rotate_scale(-1, 1)
|
ur = ur.rotate_scale(-1, 1)
|
||||||
elif rotate == 180:
|
elif rotate == 180:
|
||||||
|
|
@ -93,22 +104,24 @@ class geometry:
|
||||||
elif rotate == 270:
|
elif rotate == 270:
|
||||||
ll = ll.rotate_scale(1, -1)
|
ll = ll.rotate_scale(1, -1)
|
||||||
ur = ur.rotate_scale(1, -1)
|
ur = ur.rotate_scale(1, -1)
|
||||||
|
else:
|
||||||
|
debug.error("Invalid rotation: {}".format(rotate), -1)
|
||||||
|
|
||||||
self.boundary = [offset + ll, offset + ur]
|
self.boundary = [offset + ll, offset + ur]
|
||||||
self.normalize()
|
self.normalize()
|
||||||
|
|
||||||
def ll(self):
|
def ll(self):
|
||||||
""" Return the lower left corner """
|
""" Return the lower left corner """
|
||||||
return self.boundary[0]
|
return self.boundary[0]
|
||||||
|
|
||||||
def ur(self):
|
def ur(self):
|
||||||
""" Return the upper right corner """
|
""" Return the upper right corner """
|
||||||
return self.boundary[1]
|
return self.boundary[1]
|
||||||
|
|
||||||
def lr(self):
|
def lr(self):
|
||||||
""" Return the lower right corner """
|
""" Return the lower right corner """
|
||||||
return vector(self.boundary[1].x, self.boundary[0].y)
|
return vector(self.boundary[1].x, self.boundary[0].y)
|
||||||
|
|
||||||
def ul(self):
|
def ul(self):
|
||||||
""" Return the upper left corner """
|
""" Return the upper left corner """
|
||||||
return vector(self.boundary[0].x, self.boundary[1].y)
|
return vector(self.boundary[0].x, self.boundary[1].y)
|
||||||
|
|
@ -132,11 +145,16 @@ class geometry:
|
||||||
def cx(self):
|
def cx(self):
|
||||||
""" Return the center x """
|
""" Return the center x """
|
||||||
return 0.5 * (self.boundary[0].x + self.boundary[1].x)
|
return 0.5 * (self.boundary[0].x + self.boundary[1].x)
|
||||||
|
|
||||||
def cy(self):
|
def cy(self):
|
||||||
""" Return the center y """
|
""" Return the center y """
|
||||||
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
||||||
|
|
||||||
|
def center(self):
|
||||||
|
""" Return the center coordinate """
|
||||||
|
return vector(self.cx(), self.cy())
|
||||||
|
|
||||||
|
|
||||||
class instance(geometry):
|
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
|
||||||
|
|
@ -147,7 +165,7 @@ class instance(geometry):
|
||||||
geometry.__init__(self)
|
geometry.__init__(self)
|
||||||
debug.check(mirror not in ["R90", "R180", "R270"],
|
debug.check(mirror not in ["R90", "R180", "R270"],
|
||||||
"Please use rotation and not mirroring during instantiation.")
|
"Please use rotation and not mirroring during instantiation.")
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.mod = mod
|
self.mod = mod
|
||||||
self.gds = mod.gds
|
self.gds = mod.gds
|
||||||
|
|
@ -165,10 +183,10 @@ class instance(geometry):
|
||||||
self.width = round_to_grid(mod.width)
|
self.width = round_to_grid(mod.width)
|
||||||
self.height = round_to_grid(mod.height)
|
self.height = round_to_grid(mod.height)
|
||||||
self.compute_boundary(offset, mirror, rotate)
|
self.compute_boundary(offset, mirror, rotate)
|
||||||
|
|
||||||
debug.info(4, "creating instance: " + self.name)
|
debug.info(4, "creating instance: " + self.name)
|
||||||
|
|
||||||
def get_blockages(self, layer, top=False):
|
def get_blockages(self, lpp, top=False):
|
||||||
""" Retrieve blockages of all modules in this instance.
|
""" Retrieve blockages of all modules in this instance.
|
||||||
Apply the transform of the instance placement to give absolute blockages."""
|
Apply the transform of the instance placement to give absolute blockages."""
|
||||||
angle = math.radians(float(self.rotate))
|
angle = math.radians(float(self.rotate))
|
||||||
|
|
@ -192,20 +210,19 @@ class instance(geometry):
|
||||||
if self.mod.is_library_cell:
|
if self.mod.is_library_cell:
|
||||||
# Writes library cell blockages as shapes instead of a large metal blockage
|
# Writes library cell blockages as shapes instead of a large metal blockage
|
||||||
blockages = []
|
blockages = []
|
||||||
blockages = self.mod.gds.getBlockages(layer)
|
blockages = self.mod.gds.getBlockages(lpp)
|
||||||
for b in blockages:
|
for b in blockages:
|
||||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||||
else:
|
else:
|
||||||
blockages = self.mod.get_blockages(layer)
|
blockages = self.mod.get_blockages(lpp)
|
||||||
for b in blockages:
|
for b in blockages:
|
||||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||||
return new_blockages
|
return new_blockages
|
||||||
|
|
||||||
|
|
||||||
def gds_write_file(self, new_layout):
|
def gds_write_file(self, new_layout):
|
||||||
"""Recursively writes all the sub-modules in this instance"""
|
"""Recursively writes all the sub-modules in this instance"""
|
||||||
debug.info(4, "writing instance: " + self.name)
|
debug.info(4, "writing instance: " + self.name)
|
||||||
# make sure to write out my module/structure
|
# make sure to write out my module/structure
|
||||||
# (it will only be written the first time though)
|
# (it will only be written the first time though)
|
||||||
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
|
||||||
|
|
@ -214,7 +231,7 @@ class instance(geometry):
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
mirror=self.mirror,
|
mirror=self.mirror,
|
||||||
rotate=self.rotate)
|
rotate=self.rotate)
|
||||||
|
|
||||||
def place(self, offset, mirror="R0", rotate=0):
|
def place(self, offset, mirror="R0", rotate=0):
|
||||||
""" This updates the placement of an instance. """
|
""" This updates the placement of an instance. """
|
||||||
# Update the placement of an already added instance
|
# Update the placement of an already added instance
|
||||||
|
|
@ -222,27 +239,27 @@ class instance(geometry):
|
||||||
self.mirror = mirror
|
self.mirror = mirror
|
||||||
self.rotate = rotate
|
self.rotate = rotate
|
||||||
self.update_boundary()
|
self.update_boundary()
|
||||||
debug.info(3, "placing instance {}".format(self))
|
debug.info(3, "placing instance {}".format(self))
|
||||||
|
|
||||||
def get_pin(self,name,index=-1):
|
def get_pin(self, name, index=-1):
|
||||||
""" Return an absolute pin that is offset and transformed based on
|
""" Return an absolute pin that is offset and transformed based on
|
||||||
this instance location. Index will return one of several pins."""
|
this instance location. Index will return one of several pins."""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
if index == -1:
|
if index == -1:
|
||||||
pin = copy.deepcopy(self.mod.get_pin(name))
|
pin = copy.deepcopy(self.mod.get_pin(name))
|
||||||
pin.transform(self.offset,self.mirror,self.rotate)
|
pin.transform(self.offset, self.mirror, self.rotate)
|
||||||
return pin
|
return pin
|
||||||
else:
|
else:
|
||||||
pins = copy.deepcopy(self.mod.get_pin(name))
|
pins = copy.deepcopy(self.mod.get_pin(name))
|
||||||
pin.transform(self.offset,self.mirror,self.rotate)
|
pins.transform(self.offset, self.mirror, self.rotate)
|
||||||
return pin[index]
|
return pin[index]
|
||||||
|
|
||||||
def get_num_pins(self, name):
|
def get_num_pins(self, name):
|
||||||
""" Return the number of pins of a given name """
|
""" Return the number of pins of a given name """
|
||||||
return len(self.mod.get_pins(name))
|
return len(self.mod.get_pins(name))
|
||||||
|
|
||||||
def get_pins(self,name):
|
def get_pins(self, name):
|
||||||
""" Return an absolute pin that is offset and transformed based on
|
""" Return an absolute pin that is offset and transformed based on
|
||||||
this instance location. """
|
this instance location. """
|
||||||
|
|
||||||
|
|
@ -251,7 +268,7 @@ class instance(geometry):
|
||||||
|
|
||||||
new_pins = []
|
new_pins = []
|
||||||
for p in pin:
|
for p in pin:
|
||||||
p.transform(self.offset,self.mirror,self.rotate)
|
p.transform(self.offset, self.mirror, self.rotate)
|
||||||
new_pins.append(p)
|
new_pins.append(p)
|
||||||
return new_pins
|
return new_pins
|
||||||
|
|
||||||
|
|
@ -391,14 +408,16 @@ class instance(geometry):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
|
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
|
||||||
|
|
||||||
|
|
||||||
class path(geometry):
|
class path(geometry):
|
||||||
"""Represents a Path"""
|
"""Represents a Path"""
|
||||||
|
|
||||||
def __init__(self, layerNumber, coordinates, path_width):
|
def __init__(self, lpp, coordinates, path_width):
|
||||||
"""Initializes a path for the specified layer"""
|
"""Initializes a path for the specified layer"""
|
||||||
geometry.__init__(self)
|
geometry.__init__(self)
|
||||||
self.name = "path"
|
self.name = "path"
|
||||||
self.layerNumber = layerNumber
|
self.layerNumber = lpp[0]
|
||||||
|
self.layerPurpose = lpp[1]
|
||||||
self.coordinates = map(lambda x: [x[0], x[1]], coordinates)
|
self.coordinates = map(lambda x: [x[0], x[1]], coordinates)
|
||||||
self.coordinates = vector(self.coordinates).snap_to_grid()
|
self.coordinates = vector(self.coordinates).snap_to_grid()
|
||||||
self.path_width = path_width
|
self.path_width = path_width
|
||||||
|
|
@ -411,32 +430,33 @@ class path(geometry):
|
||||||
"""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)
|
||||||
new_layout.addPath(layerNumber=self.layerNumber,
|
new_layout.addPath(layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=self.layerPurpose,
|
||||||
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. """
|
||||||
assert(0)
|
assert(0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "path: layer=" + self.layerNumber + " w=" + self.width
|
return "path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "( path: layer=" + self.layerNumber + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
return "( path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
||||||
|
|
||||||
|
|
||||||
class label(geometry):
|
class label(geometry):
|
||||||
"""Represents a text label"""
|
"""Represents a text label"""
|
||||||
|
|
||||||
def __init__(self, text, layerNumber, offset, zoom=-1):
|
def __init__(self, text, lpp, offset, zoom=-1):
|
||||||
"""Initializes a text label for specified layer"""
|
"""Initializes a text label for specified layer"""
|
||||||
geometry.__init__(self)
|
geometry.__init__(self)
|
||||||
self.name = "label"
|
self.name = "label"
|
||||||
self.text = text
|
self.text = text
|
||||||
self.layerNumber = layerNumber
|
self.layerNumber = lpp[0]
|
||||||
|
self.layerPurpose = lpp[1]
|
||||||
self.offset = vector(offset).snap_to_grid()
|
self.offset = vector(offset).snap_to_grid()
|
||||||
|
|
||||||
if zoom<0:
|
if zoom<0:
|
||||||
|
|
@ -446,14 +466,14 @@ class label(geometry):
|
||||||
|
|
||||||
self.size = 0
|
self.size = 0
|
||||||
|
|
||||||
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||||
|
|
||||||
def gds_write_file(self, new_layout):
|
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)
|
||||||
new_layout.addText(text=self.text,
|
new_layout.addText(text=self.text,
|
||||||
layerNumber=self.layerNumber,
|
layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=self.layerPurpose,
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
magnification=self.zoom,
|
magnification=self.zoom,
|
||||||
rotate=None)
|
rotate=None)
|
||||||
|
|
@ -461,24 +481,25 @@ class label(geometry):
|
||||||
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. """
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "label: " + self.text + " layer=" + str(self.layerNumber)
|
return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
|
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class rectangle(geometry):
|
class rectangle(geometry):
|
||||||
"""Represents a rectangular shape"""
|
"""Represents a rectangular shape"""
|
||||||
|
|
||||||
def __init__(self, layerNumber, offset, width, height):
|
def __init__(self, lpp, offset, width, height):
|
||||||
"""Initializes a rectangular shape for specified layer"""
|
"""Initializes a rectangular shape for specified layer"""
|
||||||
geometry.__init__(self)
|
geometry.__init__(self)
|
||||||
self.name = "rect"
|
self.name = "rect"
|
||||||
self.layerNumber = layerNumber
|
self.layerNumber = lpp[0]
|
||||||
|
self.layerPurpose = lpp[1]
|
||||||
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 = round_to_grid(self.size.x)
|
self.width = round_to_grid(self.size.x)
|
||||||
|
|
@ -487,7 +508,7 @@ class rectangle(geometry):
|
||||||
|
|
||||||
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
||||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||||
|
|
||||||
def get_blockages(self, layer):
|
def get_blockages(self, layer):
|
||||||
""" Returns a list of one rectangle if it is on this layer"""
|
""" Returns a list of one rectangle if it is on this layer"""
|
||||||
if self.layerNumber == layer:
|
if self.layerNumber == layer:
|
||||||
|
|
@ -502,7 +523,7 @@ class rectangle(geometry):
|
||||||
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))
|
||||||
new_layout.addBox(layerNumber=self.layerNumber,
|
new_layout.addBox(layerNumber=self.layerNumber,
|
||||||
purposeNumber=0,
|
purposeNumber=self.layerPurpose,
|
||||||
offsetInMicrons=self.offset,
|
offsetInMicrons=self.offset,
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=self.height,
|
height=self.height,
|
||||||
|
|
@ -514,4 +535,4 @@ class rectangle(geometry):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " )"
|
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,10 @@
|
||||||
#
|
#
|
||||||
import hierarchy_layout
|
import hierarchy_layout
|
||||||
import hierarchy_spice
|
import hierarchy_spice
|
||||||
import globals
|
|
||||||
import verify
|
|
||||||
import debug
|
import debug
|
||||||
import os
|
import os
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
import graph_util
|
import tech
|
||||||
|
|
||||||
total_drc_errors = 0
|
|
||||||
total_lvs_errors = 0
|
|
||||||
|
|
||||||
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,126 +23,165 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
||||||
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
||||||
|
|
||||||
|
# If we have a separate lvs directory, then all the lvs files
|
||||||
|
# should be in there (all or nothing!)
|
||||||
|
try:
|
||||||
|
lvs_subdir = tech.lvs_lib
|
||||||
|
except AttributeError:
|
||||||
|
lvs_subdir = "lvs_lib"
|
||||||
|
lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
|
||||||
|
|
||||||
|
if os.path.exists(lvs_dir):
|
||||||
|
self.lvs_file = lvs_dir + name + ".sp"
|
||||||
|
else:
|
||||||
|
self.lvs_file = self.sp_file
|
||||||
|
|
||||||
|
self.drc_errors = "skipped"
|
||||||
|
self.lvs_errors = "skipped"
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
hierarchy_spice.spice.__init__(self, name)
|
hierarchy_spice.spice.__init__(self, name)
|
||||||
hierarchy_layout.layout.__init__(self, name)
|
hierarchy_layout.layout.__init__(self, name)
|
||||||
self.init_graph_params()
|
self.init_graph_params()
|
||||||
|
|
||||||
def get_layout_pins(self,inst):
|
def get_layout_pins(self, inst):
|
||||||
""" Return a map of pin locations of the instance offset """
|
""" Return a map of pin locations of the instance offset """
|
||||||
# find the instance
|
# find the instance
|
||||||
for i in self.insts:
|
for i in self.insts:
|
||||||
if i.name == inst.name:
|
if i.name == inst.name:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
debug.error("Couldn't find instance {0}".format(inst_name),-1)
|
debug.error("Couldn't find instance {0}".format(inst.name), -1)
|
||||||
inst_map = inst.mod.pin_map
|
inst_map = inst.mod.pin_map
|
||||||
return inst_map
|
return inst_map
|
||||||
|
|
||||||
|
def DRC_LVS(self, final_verification=False, force_check=False):
|
||||||
def DRC_LVS(self, final_verification=False, top_level=False):
|
|
||||||
"""Checks both DRC and LVS for a module"""
|
"""Checks both DRC and LVS for a module"""
|
||||||
|
import verify
|
||||||
# Final verification option does not allow nets to be connected by label.
|
|
||||||
# Unit tests will check themselves.
|
# No layout to check
|
||||||
if OPTS.is_unit_test:
|
if OPTS.netlist_only:
|
||||||
return
|
return
|
||||||
if not OPTS.check_lvsdrc:
|
# Unit tests will check themselves.
|
||||||
|
elif not force_check and OPTS.is_unit_test:
|
||||||
|
return
|
||||||
|
elif not force_check and not OPTS.check_lvsdrc:
|
||||||
return
|
return
|
||||||
# Do not run if disabled in options.
|
# Do not run if disabled in options.
|
||||||
if (OPTS.inline_lvsdrc or top_level):
|
elif (OPTS.inline_lvsdrc or force_check or final_verification):
|
||||||
|
|
||||||
global total_drc_errors
|
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||||
global total_lvs_errors
|
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
self.lvs_write(tempspice)
|
||||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
|
||||||
self.sp_write(tempspice)
|
|
||||||
self.gds_write(tempgds)
|
self.gds_write(tempgds)
|
||||||
|
# Final verification option does not allow nets to be connected by label.
|
||||||
|
self.drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
||||||
|
self.lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||||
|
|
||||||
num_drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
# force_check is used to determine decoder height and other things, so we shouldn't fail
|
||||||
num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
# if that flag is set
|
||||||
debug.check(num_drc_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_drc_errors))
|
if OPTS.inline_lvsdrc and not force_check:
|
||||||
debug.check(num_lvs_errors == 0,"LVS failed for {0} with {1} errors(s)".format(self.name,num_lvs_errors))
|
debug.check(self.drc_errors == 0,
|
||||||
total_drc_errors += num_drc_errors
|
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||||
total_lvs_errors += num_lvs_errors
|
self.drc_errors))
|
||||||
|
debug.check(self.lvs_errors == 0,
|
||||||
os.remove(tempspice)
|
"LVS failed for {0} with {1} errors(s)".format(self.name,
|
||||||
os.remove(tempgds)
|
self.lvs_errors))
|
||||||
|
|
||||||
|
if OPTS.purge_temp:
|
||||||
|
os.remove(tempspice)
|
||||||
|
os.remove(tempgds)
|
||||||
|
|
||||||
def DRC(self, final_verification=False):
|
def DRC(self, final_verification=False):
|
||||||
"""Checks DRC for a module"""
|
"""Checks DRC for a module"""
|
||||||
|
import verify
|
||||||
|
|
||||||
# Unit tests will check themselves.
|
# Unit tests will check themselves.
|
||||||
# Do not run if disabled in options.
|
# Do not run if disabled in options.
|
||||||
|
|
||||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
# No layout to check
|
||||||
global total_drc_errors
|
if OPTS.netlist_only:
|
||||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
return
|
||||||
|
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||||
|
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||||
self.gds_write(tempgds)
|
self.gds_write(tempgds)
|
||||||
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
||||||
total_drc_errors += num_errors
|
debug.check(num_errors == 0,
|
||||||
debug.check(num_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_error))
|
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||||
|
num_errors))
|
||||||
|
|
||||||
os.remove(tempgds)
|
if OPTS.purge_temp:
|
||||||
|
os.remove(tempgds)
|
||||||
|
|
||||||
def LVS(self, final_verification=False):
|
def LVS(self, final_verification=False):
|
||||||
"""Checks LVS for a module"""
|
"""Checks LVS for a module"""
|
||||||
|
import verify
|
||||||
|
|
||||||
# Unit tests will check themselves.
|
# Unit tests will check themselves.
|
||||||
# Do not run if disabled in options.
|
# Do not run if disabled in options.
|
||||||
|
|
||||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
# No layout to check
|
||||||
global total_lvs_errors
|
if OPTS.netlist_only:
|
||||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
return
|
||||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||||
self.sp_write(tempspice)
|
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||||
|
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||||
|
self.lvs_write(tempspice)
|
||||||
self.gds_write(tempgds)
|
self.gds_write(tempgds)
|
||||||
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||||
total_lvs_errors += num_errors
|
debug.check(num_errors == 0,
|
||||||
debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors))
|
"LVS failed for {0} with {1} error(s)".format(self.name,
|
||||||
os.remove(tempspice)
|
num_errors))
|
||||||
os.remove(tempgds)
|
if OPTS.purge_temp:
|
||||||
|
os.remove(tempspice)
|
||||||
|
os.remove(tempgds)
|
||||||
|
|
||||||
def init_graph_params(self):
|
def init_graph_params(self):
|
||||||
"""Initializes parameters relevant to the graph creation"""
|
"""Initializes parameters relevant to the graph creation"""
|
||||||
#Only initializes a set for checking instances which should not be added
|
# Only initializes a set for checking instances which should not be added
|
||||||
self.graph_inst_exclude = set()
|
self.graph_inst_exclude = set()
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Recursively create graph from instances in module."""
|
"""Recursively create graph from instances in module."""
|
||||||
|
|
||||||
#Translate port names to external nets
|
# Translate port names to external nets
|
||||||
if len(port_nets) != len(self.pins):
|
if len(port_nets) != len(self.pins):
|
||||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
self.pins),
|
||||||
|
1)
|
||||||
|
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||||
debug.info(3, "Instance name={}".format(inst_name))
|
debug.info(3, "Instance name={}".format(inst_name))
|
||||||
for subinst, conns in zip(self.insts, self.conns):
|
for subinst, conns in zip(self.insts, self.conns):
|
||||||
if subinst in self.graph_inst_exclude:
|
if subinst in self.graph_inst_exclude:
|
||||||
continue
|
continue
|
||||||
subinst_name = inst_name+'.X'+subinst.name
|
subinst_name = inst_name + '.X' + subinst.name
|
||||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||||
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
|
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
|
||||||
|
|
||||||
def build_names(self, name_dict, inst_name, port_nets):
|
def build_names(self, name_dict, inst_name, port_nets):
|
||||||
"""Collects all the nets and the parent inst of that net."""
|
"""Collects all the nets and the parent inst of that net."""
|
||||||
#Translate port names to external nets
|
# Translate port names to external nets
|
||||||
if len(port_nets) != len(self.pins):
|
if len(port_nets) != len(self.pins):
|
||||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
self.pins),
|
||||||
|
1)
|
||||||
|
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||||
debug.info(3, "Instance name={}".format(inst_name))
|
debug.info(3, "Instance name={}".format(inst_name))
|
||||||
for subinst, conns in zip(self.insts, self.conns):
|
for subinst, conns in zip(self.insts, self.conns):
|
||||||
subinst_name = inst_name+'.X'+subinst.name
|
subinst_name = inst_name + '.X' + subinst.name
|
||||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||||
for si_port, conn in zip(subinst_ports, conns):
|
for si_port, conn in zip(subinst_ports, conns):
|
||||||
#Only add for first occurrence
|
# Only add for first occurrence
|
||||||
if si_port.lower() not in name_dict:
|
if si_port.lower() not in name_dict:
|
||||||
mod_info = {'mod':self, 'int_net':conn}
|
mod_info = {'mod': self, 'int_net': conn}
|
||||||
name_dict[si_port.lower()] = mod_info
|
name_dict[si_port.lower()] = mod_info
|
||||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||||
|
|
||||||
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
||||||
"""Given a list of nets, will compare the internal alias of a mod to determine
|
"""Given a list of nets, will compare the internal alias of a mod to determine
|
||||||
if the nets have a connection to this mod's net (but not inst).
|
if the nets have a connection to this mod's net (but not inst).
|
||||||
"""
|
"""
|
||||||
if exclusion_set == None:
|
if not exclusion_set:
|
||||||
exclusion_set = set()
|
exclusion_set = set()
|
||||||
try:
|
try:
|
||||||
self.name_dict
|
self.name_dict
|
||||||
|
|
@ -161,17 +195,17 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
int_mod = self.name_dict[net]['mod']
|
int_mod = self.name_dict[net]['mod']
|
||||||
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
||||||
aliases.append(net)
|
aliases.append(net)
|
||||||
return aliases
|
return aliases
|
||||||
|
|
||||||
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
||||||
"""Checks if the alias_net in input mod is the same as the input net for this mod (self)."""
|
"""Checks if the alias_net in input mod is the same as the input net for this mod (self)."""
|
||||||
if self in exclusion_set:
|
if self in exclusion_set:
|
||||||
return False
|
return False
|
||||||
#Check ports of this mod
|
# Check ports of this mod
|
||||||
for pin in self.pins:
|
for pin in self.pins:
|
||||||
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
||||||
return True
|
return True
|
||||||
#Check connections of all other subinsts
|
# Check connections of all other subinsts
|
||||||
mod_set = set()
|
mod_set = set()
|
||||||
for subinst, inst_conns in zip(self.insts, self.conns):
|
for subinst, inst_conns in zip(self.insts, self.conns):
|
||||||
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
||||||
|
|
@ -181,7 +215,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
||||||
return True
|
return True
|
||||||
mod_set.add(subinst.mod)
|
mod_set.add(subinst.mod)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
||||||
"""Utility function for checking single net alias."""
|
"""Utility function for checking single net alias."""
|
||||||
|
|
@ -190,8 +224,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
parent_net.lower() == alias_net.lower()
|
parent_net.lower() == alias_net.lower()
|
||||||
|
|
||||||
def get_mod_net(self, parent_net, child_inst, child_conns):
|
def get_mod_net(self, parent_net, child_inst, child_conns):
|
||||||
"""Given an instance and net, returns the internal net in the mod
|
"""
|
||||||
corresponding to input net."""
|
Given an instance and net, returns the internal net in the mod
|
||||||
|
corresponding to input net.
|
||||||
|
"""
|
||||||
for conn, pin in zip(child_conns, child_inst.mod.pins):
|
for conn, pin in zip(child_conns, child_inst.mod.pins):
|
||||||
if parent_net.lower() == conn.lower():
|
if parent_net.lower() == conn.lower():
|
||||||
return pin
|
return pin
|
||||||
|
|
@ -205,27 +241,27 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
converted_conns.append(port_dict[conn])
|
converted_conns.append(port_dict[conn])
|
||||||
else:
|
else:
|
||||||
converted_conns.append("{}.{}".format(inst_name, conn))
|
converted_conns.append("{}.{}".format(inst_name, conn))
|
||||||
return converted_conns
|
return converted_conns
|
||||||
|
|
||||||
def add_graph_edges(self, graph, port_nets):
|
def add_graph_edges(self, graph, port_nets):
|
||||||
"""For every input, adds an edge to every output.
|
"""For every input, adds an edge to every output.
|
||||||
Only intended to be used for gates and other simple modules."""
|
Only intended to be used for gates and other simple modules."""
|
||||||
#The final pin names will depend on the spice hierarchy, so
|
# The final pin names will depend on the spice hierarchy, so
|
||||||
#they are passed as an input.
|
# they are passed as an input.
|
||||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||||
input_pins = self.get_inputs()
|
input_pins = self.get_inputs()
|
||||||
output_pins = self.get_outputs()
|
output_pins = self.get_outputs()
|
||||||
inout_pins = self.get_inouts()
|
inout_pins = self.get_inouts()
|
||||||
for inp in input_pins+inout_pins:
|
for inp in input_pins + inout_pins:
|
||||||
for out in output_pins+inout_pins:
|
for out in output_pins + inout_pins:
|
||||||
if inp != out: #do not add self loops
|
if inp != out: # do not add self loops
|
||||||
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
pins = ",".join(self.pins)
|
pins = ",".join(self.pins)
|
||||||
insts = [" {}".format(x) for x in self.insts]
|
insts = [" {}".format(x) for x in self.insts]
|
||||||
objs = [" {}".format(x) for x in self.objs]
|
objs = [" {}".format(x) for x in self.objs]
|
||||||
s = "********** design {0} **********".format(self.name)
|
s = "********** design {0} **********".format(self.name)
|
||||||
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
|
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
|
||||||
s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs))
|
s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs))
|
||||||
|
|
@ -236,8 +272,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
||||||
for i in self.objs:
|
for i in self.objs:
|
||||||
text+=str(i)+",\n"
|
text+=str(i) + ",\n"
|
||||||
for i in self.insts:
|
for i in self.insts:
|
||||||
text+=str(i)+",\n"
|
text+=str(i) + ",\n"
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,11 +10,12 @@ import re
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
import tech
|
import tech
|
||||||
from delay_data import *
|
from delay_data import delay_data
|
||||||
from wire_spice_model import *
|
from wire_spice_model import wire_spice_model
|
||||||
from power_data import *
|
from power_data import power_data
|
||||||
import logical_effort
|
import logical_effort
|
||||||
|
|
||||||
|
|
||||||
class spice():
|
class spice():
|
||||||
"""
|
"""
|
||||||
This provides a set of useful generic types for hierarchy
|
This provides a set of useful generic types for hierarchy
|
||||||
|
|
@ -30,19 +31,21 @@ class spice():
|
||||||
|
|
||||||
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||||
# Holds subckts/mods for this module
|
# Holds subckts/mods for this module
|
||||||
self.mods = []
|
self.mods = []
|
||||||
# Holds the pins for this module
|
# Holds the pins for this module
|
||||||
self.pins = []
|
self.pins = []
|
||||||
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
|
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
|
||||||
# for each instance, this is the set of nets/nodes that map to the pins for this instance
|
# for each instance, this is the set of nets/nodes that map to the pins for this instance
|
||||||
self.pin_type = {}
|
self.pin_type = {}
|
||||||
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
|
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
|
||||||
# Spice format)
|
# Spice format)
|
||||||
self.conns = []
|
self.conns = []
|
||||||
|
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
|
||||||
|
self.no_instances = False
|
||||||
# Keep track of any comments to add the the spice
|
# Keep track of any comments to add the the spice
|
||||||
try:
|
try:
|
||||||
self.commments
|
self.commments
|
||||||
except:
|
except AttributeError:
|
||||||
self.comments = []
|
self.comments = []
|
||||||
|
|
||||||
self.sp_read()
|
self.sp_read()
|
||||||
|
|
@ -56,7 +59,7 @@ class spice():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.commments
|
self.commments
|
||||||
except:
|
except AttributeError:
|
||||||
self.comments = []
|
self.comments = []
|
||||||
|
|
||||||
self.comments.append(comment)
|
self.comments.append(comment)
|
||||||
|
|
@ -65,7 +68,9 @@ class spice():
|
||||||
""" Adds a pin to the pins list. Default type is INOUT signal. """
|
""" Adds a pin to the pins list. Default type is INOUT signal. """
|
||||||
self.pins.append(name)
|
self.pins.append(name)
|
||||||
self.pin_type[name]=pin_type
|
self.pin_type[name]=pin_type
|
||||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
debug.check(pin_type in self.valid_signal_types,
|
||||||
|
"Invalid signaltype for {0}: {1}".format(name,
|
||||||
|
pin_type))
|
||||||
|
|
||||||
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
||||||
""" Adds a pin_list to the pins list """
|
""" Adds a pin_list to the pins list """
|
||||||
|
|
@ -73,36 +78,43 @@ class spice():
|
||||||
# or a list that is the same length as the pin list.
|
# or a list that is the same length as the pin list.
|
||||||
if type(pin_type)==str:
|
if type(pin_type)==str:
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,pin_type))
|
debug.check(pin_type in self.valid_signal_types,
|
||||||
self.add_pin(pin,pin_type)
|
"Invalid signaltype for {0}: {1}".format(pin,
|
||||||
|
pin_type))
|
||||||
|
self.add_pin(pin, pin_type)
|
||||||
|
|
||||||
elif len(pin_type)==len(pin_list):
|
elif len(pin_type)==len(pin_list):
|
||||||
for (pin,ptype) in zip(pin_list, pin_type):
|
for (pin, ptype) in zip(pin_list, pin_type):
|
||||||
debug.check(ptype in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,ptype))
|
debug.check(ptype in self.valid_signal_types,
|
||||||
self.add_pin(pin,ptype)
|
"Invalid signaltype for {0}: {1}".format(pin,
|
||||||
|
ptype))
|
||||||
|
self.add_pin(pin, ptype)
|
||||||
else:
|
else:
|
||||||
debug.error("Mismatch in type and pin list lengths.", -1)
|
debug.error("Mismatch in type and pin list lengths.", -1)
|
||||||
|
|
||||||
def add_pin_types(self, type_list):
|
def add_pin_types(self, type_list):
|
||||||
"""Add pin types for all the cell's pins.
|
"""
|
||||||
Typically, should only be used for handmade cells."""
|
Add pin types for all the cell's pins.
|
||||||
#This only works if self.pins == bitcell.pin_names
|
Typically, should only be used for handmade cells.
|
||||||
|
"""
|
||||||
|
# This only works if self.pins == bitcell.pin_names
|
||||||
if self.pin_names != self.pins:
|
if self.pin_names != self.pins:
|
||||||
debug.error("{} spice subcircuit port names do not match pin_names\
|
debug.error("{} spice subcircuit port names do not match pin_names\
|
||||||
\n SPICE names={}\
|
\n SPICE names={}\
|
||||||
\n Module names={}\
|
\n Module names={}\
|
||||||
".format(self.name, self.pin_names, self.pins),1)
|
".format(self.name, self.pin_names, self.pins), 1)
|
||||||
self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)}
|
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
|
||||||
|
|
||||||
def get_pin_type(self, name):
|
def get_pin_type(self, name):
|
||||||
""" Returns the type of the signal pin. """
|
""" Returns the type of the signal pin. """
|
||||||
pin_type = self.pin_type[name]
|
pin_type = self.pin_type[name]
|
||||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
debug.check(pin_type in self.valid_signal_types,
|
||||||
|
"Invalid signaltype for {0}: {1}".format(name, pin_type))
|
||||||
return pin_type
|
return pin_type
|
||||||
|
|
||||||
def get_pin_dir(self, name):
|
def get_pin_dir(self, name):
|
||||||
""" Returns the direction of the pin. (Supply/ground are INOUT). """
|
""" Returns the direction of the pin. (Supply/ground are INOUT). """
|
||||||
if self.pin_type[name] in ["POWER","GROUND"]:
|
if self.pin_type[name] in ["POWER", "GROUND"]:
|
||||||
return "INOUT"
|
return "INOUT"
|
||||||
else:
|
else:
|
||||||
return self.pin_type[name]
|
return self.pin_type[name]
|
||||||
|
|
@ -125,11 +137,10 @@ class spice():
|
||||||
output_list.append(pin)
|
output_list.append(pin)
|
||||||
return output_list
|
return output_list
|
||||||
|
|
||||||
|
|
||||||
def copy_pins(self, other_module, suffix=""):
|
def copy_pins(self, other_module, suffix=""):
|
||||||
""" This will copy all of the pins from the other module and add an optional suffix."""
|
""" This will copy all of the pins from the other module and add an optional suffix."""
|
||||||
for pin in other_module.pins:
|
for pin in other_module.pins:
|
||||||
self.add_pin(pin+suffix, other_module.get_pin_type(pin))
|
self.add_pin(pin + suffix, other_module.get_pin_type(pin))
|
||||||
|
|
||||||
def get_inouts(self):
|
def get_inouts(self):
|
||||||
""" These use pin types to determine pin lists. These
|
""" These use pin types to determine pin lists. These
|
||||||
|
|
@ -144,7 +155,6 @@ class spice():
|
||||||
"""Adds a subckt/submodule to the subckt hierarchy"""
|
"""Adds a subckt/submodule to the subckt hierarchy"""
|
||||||
self.mods.append(mod)
|
self.mods.append(mod)
|
||||||
|
|
||||||
|
|
||||||
def connect_inst(self, args, check=True):
|
def connect_inst(self, args, check=True):
|
||||||
"""Connects the pins of the last instance added
|
"""Connects the pins of the last instance added
|
||||||
It is preferred to use the function with the check to find if
|
It is preferred to use the function with the check to find if
|
||||||
|
|
@ -169,21 +179,23 @@ class spice():
|
||||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
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(insts_string))
|
debug.error("Instances: \n" + str(insts_string))
|
||||||
debug.error("-----")
|
debug.error("-----")
|
||||||
debug.error("Connections: \n"+str(conns_string),1)
|
debug.error("Connections: \n" + str(conns_string), 1)
|
||||||
|
|
||||||
def get_conns(self, inst):
|
def get_conns(self, inst):
|
||||||
"""Returns the connections of a given instance."""
|
"""Returns the connections of a given instance."""
|
||||||
for i in range(len(self.insts)):
|
for i in range(len(self.insts)):
|
||||||
if inst is self.insts[i]:
|
if inst is self.insts[i]:
|
||||||
return self.conns[i]
|
return self.conns[i]
|
||||||
#If not found, returns None
|
# If not found, returns None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def sp_read(self):
|
def sp_read(self):
|
||||||
"""Reads the sp file (and parse the pins) from the library
|
"""
|
||||||
Otherwise, initialize it to null for dynamic generation"""
|
Reads the sp file (and parse the pins) from the library
|
||||||
|
Otherwise, initialize it to null for dynamic generation
|
||||||
|
"""
|
||||||
if self.sp_file and os.path.isfile(self.sp_file):
|
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)
|
||||||
|
|
@ -198,17 +210,37 @@ class spice():
|
||||||
# parses line into ports and remove subckt
|
# parses line into ports and remove subckt
|
||||||
self.pins = subckt_line.split(" ")[2:]
|
self.pins = subckt_line.split(" ")[2:]
|
||||||
else:
|
else:
|
||||||
|
debug.info(4, "no spfile {0}".format(self.sp_file))
|
||||||
self.spice = []
|
self.spice = []
|
||||||
|
|
||||||
|
# We don't define self.lvs and will use self.spice if dynamically created
|
||||||
|
# or they are the same file
|
||||||
|
if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
|
||||||
|
debug.info(3, "opening {0}".format(self.lvs_file))
|
||||||
|
f = open(self.lvs_file)
|
||||||
|
self.lvs = f.readlines()
|
||||||
|
for i in range(len(self.lvs)):
|
||||||
|
self.lvs[i] = self.lvs[i].rstrip(" \n")
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# pins and subckt should be the same
|
||||||
|
# find the correct subckt line in the file
|
||||||
|
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
|
||||||
|
subckt_line = list(filter(subckt.search, self.lvs))[0]
|
||||||
|
# parses line into ports and remove subckt
|
||||||
|
lvs_pins = subckt_line.split(" ")[2:]
|
||||||
|
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
|
||||||
|
|
||||||
def check_net_in_spice(self, net_name):
|
def check_net_in_spice(self, net_name):
|
||||||
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
|
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
|
||||||
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
|
# Remove spaces and lower case then add spaces.
|
||||||
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
|
# Nets are separated by spaces.
|
||||||
|
net_formatted = ' ' + net_name.lstrip().rstrip().lower() + ' '
|
||||||
for line in self.spice:
|
for line in self.spice:
|
||||||
#Lowercase the line and remove any part of the line that is a comment.
|
# Lowercase the line and remove any part of the line that is a comment.
|
||||||
line = line.lower().split('*')[0]
|
line = line.lower().split('*')[0]
|
||||||
|
|
||||||
#Skip .subckt or .ENDS lines
|
# Skip .subckt or .ENDS lines
|
||||||
if line.find('.') == 0:
|
if line.find('.') == 0:
|
||||||
continue
|
continue
|
||||||
if net_formatted in line:
|
if net_formatted in line:
|
||||||
|
|
@ -220,7 +252,7 @@ class spice():
|
||||||
nets_match = True
|
nets_match = True
|
||||||
for net in nets:
|
for net in nets:
|
||||||
nets_match = nets_match and self.check_net_in_spice(net)
|
nets_match = nets_match and self.check_net_in_spice(net)
|
||||||
return nets_match
|
return nets_match
|
||||||
|
|
||||||
def contains(self, mod, modlist):
|
def contains(self, mod, modlist):
|
||||||
for x in modlist:
|
for x in modlist:
|
||||||
|
|
@ -228,54 +260,64 @@ class spice():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def sp_write_file(self, sp, usedMODS):
|
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
|
||||||
""" Recursive spice subcircuit write;
|
"""
|
||||||
Writes the spice subcircuit from the library or the dynamically generated one"""
|
Recursive spice subcircuit write;
|
||||||
if not self.spice:
|
Writes the spice subcircuit from the library or the dynamically generated one
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.no_instances:
|
||||||
|
return
|
||||||
|
elif not self.spice:
|
||||||
|
# If spice isn't defined, we dynamically generate one.
|
||||||
|
|
||||||
# recursively write the modules
|
# recursively write the modules
|
||||||
for i in self.mods:
|
for i in self.mods:
|
||||||
if self.contains(i, usedMODS):
|
if self.contains(i, usedMODS):
|
||||||
continue
|
continue
|
||||||
usedMODS.append(i)
|
usedMODS.append(i)
|
||||||
i.sp_write_file(sp, usedMODS)
|
i.sp_write_file(sp, usedMODS, lvs_netlist)
|
||||||
|
|
||||||
if len(self.insts) == 0:
|
if len(self.insts) == 0:
|
||||||
return
|
return
|
||||||
if self.pins == []:
|
if self.pins == []:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
# write out the first spice line (the subcircuit)
|
# write out the first spice line (the subcircuit)
|
||||||
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
|
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
|
||||||
" ".join(self.pins)))
|
" ".join(self.pins)))
|
||||||
|
|
||||||
for pin in self.pins:
|
for pin in self.pins:
|
||||||
sp.write("* {1:6}: {0} \n".format(pin,self.pin_type[pin]))
|
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
|
||||||
|
|
||||||
for line in self.comments:
|
for line in self.comments:
|
||||||
sp.write("* {}\n".format(line))
|
sp.write("* {}\n".format(line))
|
||||||
|
|
||||||
# every instance must have a set of connections, even if it is empty.
|
# every instance must have a set of connections, even if it is empty.
|
||||||
if len(self.insts)!=len(self.conns):
|
if len(self.insts) != len(self.conns):
|
||||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
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(self.insts))
|
||||||
debug.error("-----")
|
debug.error("-----")
|
||||||
debug.error("Connections: \n"+str(self.conns),1)
|
debug.error("Connections: \n" + str(self.conns), 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for i in range(len(self.insts)):
|
for i in range(len(self.insts)):
|
||||||
# we don't need to output connections of empty instances.
|
# we don't need to output connections of empty instances.
|
||||||
# these are wires and paths
|
# these are wires and paths
|
||||||
if self.conns[i] == []:
|
if self.conns[i] == []:
|
||||||
continue
|
continue
|
||||||
if hasattr(self.insts[i].mod,"spice_device"):
|
# Instance with no devices in it needs no subckt/instance
|
||||||
|
if self.insts[i].mod.no_instances:
|
||||||
|
continue
|
||||||
|
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
|
||||||
|
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
|
||||||
|
" ".join(self.conns[i])))
|
||||||
|
sp.write("\n")
|
||||||
|
elif hasattr(self.insts[i].mod, "spice_device"):
|
||||||
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
|
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
|
||||||
" ".join(self.conns[i])))
|
" ".join(self.conns[i])))
|
||||||
sp.write("\n")
|
sp.write("\n")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
|
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
|
||||||
" ".join(self.conns[i]),
|
" ".join(self.conns[i]),
|
||||||
|
|
@ -284,11 +326,14 @@ class spice():
|
||||||
sp.write(".ENDS {0}\n".format(self.name))
|
sp.write(".ENDS {0}\n".format(self.name))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# write the subcircuit itself
|
# If spice is a hard module, output the spice file contents.
|
||||||
# Including the file path makes the unit test fail for other users.
|
# Including the file path makes the unit test fail for other users.
|
||||||
#if os.path.isfile(self.sp_file):
|
# if os.path.isfile(self.sp_file):
|
||||||
# sp.write("\n* {0}\n".format(self.sp_file))
|
# sp.write("\n* {0}\n".format(self.sp_file))
|
||||||
sp.write("\n".join(self.spice))
|
if lvs_netlist and hasattr(self, "lvs"):
|
||||||
|
sp.write("\n".join(self.lvs))
|
||||||
|
else:
|
||||||
|
sp.write("\n".join(self.spice))
|
||||||
|
|
||||||
sp.write("\n")
|
sp.write("\n")
|
||||||
|
|
||||||
|
|
@ -302,21 +347,32 @@ class spice():
|
||||||
del usedMODS
|
del usedMODS
|
||||||
spfile.close()
|
spfile.close()
|
||||||
|
|
||||||
|
def lvs_write(self, spname):
|
||||||
|
"""Writes the lvs to files"""
|
||||||
|
debug.info(3, "Writing to {0}".format(spname))
|
||||||
|
spfile = open(spname, 'w')
|
||||||
|
spfile.write("*FIRST LINE IS A COMMENT\n")
|
||||||
|
usedMODS = list()
|
||||||
|
self.sp_write_file(spfile, usedMODS, True)
|
||||||
|
del usedMODS
|
||||||
|
spfile.close()
|
||||||
|
|
||||||
def analytical_delay(self, corner, slew, load=0.0):
|
def analytical_delay(self, corner, slew, load=0.0):
|
||||||
"""Inform users undefined delay module while building new modules"""
|
"""Inform users undefined delay module while building new modules"""
|
||||||
|
|
||||||
# FIXME: Slew is not used in the model right now. Can be added heuristically as linear factor
|
# FIXME: Slew is not used in the model right now.
|
||||||
|
# Can be added heuristically as linear factor
|
||||||
relative_cap = logical_effort.convert_farad_to_relative_c(load)
|
relative_cap = logical_effort.convert_farad_to_relative_c(load)
|
||||||
stage_effort = self.get_stage_effort(relative_cap)
|
stage_effort = self.get_stage_effort(relative_cap)
|
||||||
|
|
||||||
# If it fails, then keep running with a valid object.
|
# If it fails, then keep running with a valid object.
|
||||||
if stage_effort == None:
|
if not stage_effort:
|
||||||
return delay_data(0.0, 0.0)
|
return delay_data(0.0, 0.0)
|
||||||
|
|
||||||
abs_delay = stage_effort.get_absolute_delay()
|
abs_delay = stage_effort.get_absolute_delay()
|
||||||
corner_delay = self.apply_corners_analytically(abs_delay, corner)
|
corner_delay = self.apply_corners_analytically(abs_delay, corner)
|
||||||
SLEW_APPROXIMATION = 0.1
|
SLEW_APPROXIMATION = 0.1
|
||||||
corner_slew = SLEW_APPROXIMATION*corner_delay
|
corner_slew = SLEW_APPROXIMATION * corner_delay
|
||||||
return delay_data(corner_delay, corner_slew)
|
return delay_data(corner_delay, corner_slew)
|
||||||
|
|
||||||
def get_stage_effort(self, cout, inp_is_rise=True):
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||||
|
|
@ -326,7 +382,7 @@ class spice():
|
||||||
debug.warning("Class {0} name {1}"
|
debug.warning("Class {0} name {1}"
|
||||||
.format(self.__class__.__name__,
|
.format(self.__class__.__name__,
|
||||||
self.name))
|
self.name))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_cin(self):
|
def get_cin(self):
|
||||||
"""Returns input load in Femto-Farads. All values generated using
|
"""Returns input load in Femto-Farads. All values generated using
|
||||||
|
|
@ -342,35 +398,35 @@ class spice():
|
||||||
debug.warning("Design Class {0} input capacitance function needs to be defined"
|
debug.warning("Design Class {0} input capacitance function needs to be defined"
|
||||||
.format(self.__class__.__name__))
|
.format(self.__class__.__name__))
|
||||||
debug.warning("Class {0} name {1}"
|
debug.warning("Class {0} name {1}"
|
||||||
.format(self.__class__.__name__,
|
.format(self.__class__.__name__,
|
||||||
self.name))
|
self.name))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5):
|
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
|
||||||
"""
|
"""
|
||||||
Calculate the delay of a mosfet by
|
Calculate the delay of a mosfet by
|
||||||
modeling it as a resistance driving a capacitance
|
modeling it as a resistance driving a capacitance
|
||||||
"""
|
"""
|
||||||
swing_factor = abs(math.log(1-swing)) # time constant based on swing
|
swing_factor = abs(math.log(1 - swing)) # time constant based on swing
|
||||||
delay = swing_factor * r * c #c is in ff and delay is in fs
|
delay = swing_factor * r * c # c is in ff and delay is in fs
|
||||||
delay = self.apply_corners_analytically(delay, corner)
|
delay = self.apply_corners_analytically(delay, corner)
|
||||||
delay = delay * 0.001 #make the unit to ps
|
delay = delay * 0.001 # make the unit to ps
|
||||||
|
|
||||||
# Output slew should be linear to input slew which is described
|
# Output slew should be linear to input slew which is described
|
||||||
# as 0.005* slew.
|
# as 0.005* slew.
|
||||||
|
|
||||||
# The slew will be also influenced by the delay.
|
# The slew will be also influenced by the delay.
|
||||||
# If no input slew(or too small to make impact)
|
# If no input slew(or too small to make impact)
|
||||||
# The mimum slew should be the time to charge RC.
|
# The mimum slew should be the time to charge RC.
|
||||||
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
|
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
|
||||||
slew = delay * 0.6 * 2 + 0.005 * slew
|
slew = delay * 0.6 * 2 + 0.005 * slew
|
||||||
return delay_data(delay = delay, slew = slew)
|
return delay_data(delay=delay, slew=slew)
|
||||||
|
|
||||||
def apply_corners_analytically(self, delay, corner):
|
def apply_corners_analytically(self, delay, corner):
|
||||||
"""Multiply delay by corner factors"""
|
"""Multiply delay by corner factors"""
|
||||||
proc,vdd,temp = corner
|
proc, vdd, temp = corner
|
||||||
#FIXME: type of delay is needed to know which process to use.
|
# FIXME: type of delay is needed to know which process to use.
|
||||||
proc_mult = max(self.get_process_delay_factor(proc))
|
proc_mult = max(self.get_process_delay_factor(proc))
|
||||||
volt_mult = self.get_voltage_delay_factor(vdd)
|
volt_mult = self.get_voltage_delay_factor(vdd)
|
||||||
temp_mult = self.get_temp_delay_factor(temp)
|
temp_mult = self.get_temp_delay_factor(temp)
|
||||||
return delay * proc_mult * volt_mult * temp_mult
|
return delay * proc_mult * volt_mult * temp_mult
|
||||||
|
|
@ -385,48 +441,51 @@ class spice():
|
||||||
elif mos_proc == 'F':
|
elif mos_proc == 'F':
|
||||||
proc_factors.append(0.9)
|
proc_factors.append(0.9)
|
||||||
elif mos_proc == 'S':
|
elif mos_proc == 'S':
|
||||||
proc_factors.append(1.1)
|
proc_factors.append(1.1)
|
||||||
return proc_factors
|
return proc_factors
|
||||||
|
|
||||||
def get_voltage_delay_factor(self, voltage):
|
def get_voltage_delay_factor(self, voltage):
|
||||||
"""Returns delay increase due to voltage.
|
"""Returns delay increase due to voltage.
|
||||||
Implemented as linear factor based off nominal voltage.
|
Implemented as linear factor based off nominal voltage.
|
||||||
"""
|
"""
|
||||||
return tech.spice["nom_supply_voltage"]/voltage
|
return tech.spice["nom_supply_voltage"] / voltage
|
||||||
|
|
||||||
def get_temp_delay_factor(self, temp):
|
def get_temp_delay_factor(self, temp):
|
||||||
"""Returns delay increase due to temperature (in C).
|
"""Returns delay increase due to temperature (in C).
|
||||||
Determines effect on threshold voltage and then linear factor is estimated.
|
Determines effect on threshold voltage and then linear factor is estimated.
|
||||||
"""
|
"""
|
||||||
#Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
# Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
||||||
#(k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
# (k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
||||||
thermal_voltage_nom = 0.008625*tech.spice["nom_temperature"]
|
thermal_voltage_nom = 0.008625 * tech.spice["nom_temperature"]
|
||||||
thermal_voltage = 0.008625*temp
|
thermal_voltage = 0.008625 * temp
|
||||||
vthresh = (tech.spice["nom_threshold"]+2*(thermal_voltage-thermal_voltage_nom))
|
vthresh = (tech.spice["nom_threshold"] + 2 * (thermal_voltage - thermal_voltage_nom))
|
||||||
#Calculate effect on Vdd-Vth. The current vdd is not used here. A separate vdd factor is calculated.
|
# Calculate effect on Vdd-Vth.
|
||||||
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"])/(tech.spice["nom_supply_voltage"]-vthresh)
|
# The current vdd is not used here.
|
||||||
|
# A separate vdd factor is calculated.
|
||||||
|
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"]) / (tech.spice["nom_supply_voltage"] - vthresh)
|
||||||
|
|
||||||
def return_delay(self, delay, slew):
|
def return_delay(self, delay, slew):
|
||||||
return delay_data(delay, slew)
|
return delay_data(delay, slew)
|
||||||
|
|
||||||
def generate_rc_net(self,lump_num, wire_length, wire_width):
|
def generate_rc_net(self, lump_num, wire_length, wire_width):
|
||||||
return wire_spice_model(lump_num, wire_length, wire_width)
|
return wire_spice_model(lump_num, wire_length, wire_width)
|
||||||
|
|
||||||
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
|
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
|
||||||
"""
|
"""
|
||||||
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
|
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
|
||||||
"""
|
"""
|
||||||
proc,vdd,temp = corner
|
proc, vdd, temp = corner
|
||||||
net_vswing = vdd*swing
|
net_vswing = vdd * swing
|
||||||
power_dyn = c*vdd*net_vswing*freq
|
power_dyn = c * vdd * net_vswing * freq
|
||||||
|
|
||||||
#Apply process and temperature factors. Roughly, process and Vdd affect the delay which affects the power.
|
# A pply process and temperature factors.
|
||||||
#No other estimations are currently used. Increased delay->slower freq.->less power
|
# Roughly, process and Vdd affect the delay which affects the power.
|
||||||
proc_div = max(self.get_process_delay_factor(proc))
|
# No other estimations are currently used. Increased delay->slower freq.->less power
|
||||||
|
proc_div = max(self.get_process_delay_factor(proc))
|
||||||
temp_div = self.get_temp_delay_factor(temp)
|
temp_div = self.get_temp_delay_factor(temp)
|
||||||
power_dyn = power_dyn/(proc_div*temp_div)
|
power_dyn = power_dyn / (proc_div * temp_div)
|
||||||
|
|
||||||
return power_dyn
|
return power_dyn
|
||||||
|
|
||||||
def return_power(self, dynamic=0.0, leakage=0.0):
|
def return_power(self, dynamic=0.0, leakage=0.0):
|
||||||
return power_data(dynamic, leakage)
|
return power_data(dynamic, leakage)
|
||||||
|
|
|
||||||
|
|
@ -8,72 +8,117 @@
|
||||||
import debug
|
import debug
|
||||||
from tech import GDS, drc
|
from tech import GDS, drc
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from tech import layer
|
from tech import layer, layer_indices
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
|
||||||
class pin_layout:
|
class pin_layout:
|
||||||
"""
|
"""
|
||||||
A class to represent a rectangular design pin. It is limited to a
|
A class to represent a rectangular design pin. It is limited to a
|
||||||
single shape.
|
single shape.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, rect, layer_name_num):
|
def __init__(self, name, rect, layer_name_pp):
|
||||||
self.name = name
|
self.name = name
|
||||||
# repack the rect as a vector, just in case
|
# repack the rect as a vector, just in case
|
||||||
if type(rect[0])==vector:
|
if type(rect[0]) == vector:
|
||||||
self.rect = rect
|
self._rect = rect
|
||||||
else:
|
else:
|
||||||
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.width() > 0, "Zero width pin.")
|
||||||
debug.check(self.height()>0,"Zero height pin.")
|
debug.check(self.height() > 0, "Zero height pin.")
|
||||||
|
|
||||||
|
# These are the valid pin layers
|
||||||
|
valid_layers = { x: layer[x] for x in layer_indices.keys()}
|
||||||
|
|
||||||
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
# if it's a string, use the name
|
||||||
if type(layer_name_num)==int:
|
if type(layer_name_pp) == str:
|
||||||
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
self._layer = layer_name_pp
|
||||||
|
# else it is required to be a lpp
|
||||||
else:
|
else:
|
||||||
self.layer=layer_name_num
|
for (layer_name, lpp) in valid_layers.items():
|
||||||
self.layer_num = layer[self.layer]
|
if not lpp:
|
||||||
|
continue
|
||||||
|
if self.same_lpp(layer_name_pp, lpp):
|
||||||
|
self._layer = layer_name
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
debug.error("Couldn't find layer {}".format(layer_name_pp), -1)
|
||||||
|
|
||||||
|
self.lpp = layer[self.layer]
|
||||||
|
self._recompute_hash()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def layer(self):
|
||||||
|
return self._layer
|
||||||
|
|
||||||
|
@layer.setter
|
||||||
|
def layer(self, l):
|
||||||
|
self._layer = l
|
||||||
|
self._recompute_hash()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rect(self):
|
||||||
|
return self._rect
|
||||||
|
|
||||||
|
@rect.setter
|
||||||
|
def rect(self, r):
|
||||||
|
self._rect = r
|
||||||
|
self._recompute_hash()
|
||||||
|
|
||||||
|
def _recompute_hash(self):
|
||||||
|
""" Recompute the hash for our hash cache """
|
||||||
|
self._hash = hash(repr(self))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
return "({} layer={} ll={} ur={})".format(self.name,
|
||||||
|
self.layer,
|
||||||
|
self.rect[0],
|
||||||
|
self.rect[1])
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""
|
"""
|
||||||
override repr function output (don't include
|
override repr function output (don't include
|
||||||
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
||||||
"""
|
"""
|
||||||
return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1])
|
return "(layer={} ll={} ur={})".format(self.layer,
|
||||||
|
self.rect[0],
|
||||||
|
self.rect[1])
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
""" Implement the hash function for sets etc. """
|
"""
|
||||||
return hash(repr(self))
|
Implement the hash function for sets etc. We only return a cached
|
||||||
|
value, that is updated when either 'rect' or 'layer' are changed. This
|
||||||
|
is a major speedup, if pin_layout is used as a key for dicts.
|
||||||
|
"""
|
||||||
|
return self._hash
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
""" Provide a function for ordering items by the ll point """
|
""" Provide a function for ordering items by the ll point """
|
||||||
(ll, ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll, our) = other.rect
|
(oll, our) = other.rect
|
||||||
|
|
||||||
if ll.x < oll.x and ll.y < oll.y:
|
if ll.x < oll.x and ll.y < oll.y:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
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.layer==other.layer and self.rect == other.rect)
|
return (self.lpp == other.lpp and self.rect == other.rect)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def bbox(self, pin_list):
|
def bbox(self, pin_list):
|
||||||
"""
|
"""
|
||||||
Given a list of layout pins, create a bounding box layout.
|
Given a list of layout pins, create a bounding box layout.
|
||||||
"""
|
"""
|
||||||
(ll, ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
min_x = ll.x
|
min_x = ll.x
|
||||||
max_x = ur.x
|
max_x = ur.x
|
||||||
min_y = ll.y
|
min_y = ll.y
|
||||||
|
|
@ -85,39 +130,46 @@ class pin_layout:
|
||||||
min_y = min(min_y, pin.ll().y)
|
min_y = min(min_y, pin.ll().y)
|
||||||
max_y = max(max_y, pin.ur().y)
|
max_y = max(max_y, pin.ur().y)
|
||||||
|
|
||||||
self.rect = [vector(min_x,min_y),vector(max_x,max_y)]
|
self.rect = [vector(min_x, min_y), vector(max_x, max_y)]
|
||||||
|
|
||||||
|
def fix_minarea(self):
|
||||||
|
"""
|
||||||
|
Try to fix minimum area rule.
|
||||||
|
"""
|
||||||
|
min_area = drc("{}_minarea".format(self.layer))
|
||||||
|
pass
|
||||||
|
|
||||||
def inflate(self, spacing=None):
|
def inflate(self, spacing=None):
|
||||||
"""
|
"""
|
||||||
Inflate the rectangle by the spacing (or other rule)
|
Inflate the rectangle by the spacing (or other rule)
|
||||||
and return the new rectangle.
|
and return the new rectangle.
|
||||||
"""
|
"""
|
||||||
if not spacing:
|
if not spacing:
|
||||||
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||||
|
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
spacing = vector(spacing, spacing)
|
spacing = vector(spacing, spacing)
|
||||||
newll = ll - spacing
|
newll = ll - spacing
|
||||||
newur = ur + spacing
|
newur = ur + spacing
|
||||||
|
|
||||||
return (newll, newur)
|
return (newll, newur)
|
||||||
|
|
||||||
def intersection(self, other):
|
def intersection(self, other):
|
||||||
""" Check if a shape overlaps with a rectangle """
|
""" Check if a shape overlaps with a rectangle """
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll, our) = other.rect
|
||||||
|
|
||||||
min_x = max(ll.x, oll.x)
|
min_x = max(ll.x, oll.x)
|
||||||
max_x = min(ll.x, oll.x)
|
max_x = min(ll.x, oll.x)
|
||||||
min_y = max(ll.y, oll.y)
|
min_y = max(ll.y, oll.y)
|
||||||
max_y = min(ll.y, oll.y)
|
max_y = min(ll.y, oll.y)
|
||||||
|
|
||||||
return [vector(min_x,min_y),vector(max_x,max_y)]
|
return [vector(min_x, min_y), vector(max_x, max_y)]
|
||||||
|
|
||||||
def xoverlaps(self, other):
|
def xoverlaps(self, other):
|
||||||
""" Check if shape has x overlap """
|
""" Check if shape has x overlap """
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll, our) = other.rect
|
||||||
x_overlaps = False
|
x_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):
|
||||||
|
|
@ -130,8 +182,8 @@ class pin_layout:
|
||||||
|
|
||||||
def yoverlaps(self, other):
|
def yoverlaps(self, other):
|
||||||
""" Check if shape has x overlap """
|
""" Check if shape has x overlap """
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll, our) = other.rect
|
||||||
y_overlaps = False
|
y_overlaps = False
|
||||||
|
|
||||||
# check if self is within other y range
|
# check if self is within other y range
|
||||||
|
|
@ -142,29 +194,29 @@ class pin_layout:
|
||||||
y_overlaps = True
|
y_overlaps = True
|
||||||
|
|
||||||
return y_overlaps
|
return y_overlaps
|
||||||
|
|
||||||
def xcontains(self, other):
|
def xcontains(self, other):
|
||||||
""" Check if shape contains the x overlap """
|
""" Check if shape contains the x overlap """
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll, our) = other.rect
|
||||||
|
|
||||||
return (oll.x >= ll.x and our.x <= ur.x)
|
return (oll.x >= ll.x and our.x <= ur.x)
|
||||||
|
|
||||||
def ycontains(self, other):
|
def ycontains(self, other):
|
||||||
""" Check if shape contains the y overlap """
|
""" Check if shape contains the y overlap """
|
||||||
(ll,ur) = self.rect
|
(ll, ur) = self.rect
|
||||||
(oll,our) = other.rect
|
(oll, our) = other.rect
|
||||||
|
|
||||||
return (oll.y >= ll.y and our.y <= ur.y)
|
return (oll.y >= ll.y and our.y <= ur.y)
|
||||||
|
|
||||||
def contains(self, other):
|
def contains(self, other):
|
||||||
""" Check if a shape contains another rectangle """
|
""" Check if a shape contains another rectangle """
|
||||||
# If it is the same shape entirely, it is contained!
|
# If it is the same shape entirely, it is contained!
|
||||||
if self == other:
|
if self == other:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Can only overlap on the same layer
|
# Can only overlap on the same layer
|
||||||
if self.layer != other.layer:
|
if not self.same_lpp(self.lpp, other.lpp):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.xcontains(other):
|
if not self.xcontains(other):
|
||||||
|
|
@ -181,14 +233,13 @@ class pin_layout:
|
||||||
if shape.contains(self):
|
if shape.contains(self):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def overlaps(self, other):
|
def overlaps(self, other):
|
||||||
""" Check if a shape overlaps with a rectangle """
|
""" Check if a shape overlaps with a rectangle """
|
||||||
# Can only overlap on the same layer
|
# Can only overlap on the same layer
|
||||||
if self.layer != other.layer:
|
if not self.same_lpp(self.lpp, other.lpp):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
x_overlaps = self.xoverlaps(other)
|
x_overlaps = self.xoverlaps(other)
|
||||||
y_overlaps = self.yoverlaps(other)
|
y_overlaps = self.yoverlaps(other)
|
||||||
|
|
||||||
|
|
@ -197,51 +248,56 @@ class pin_layout:
|
||||||
def area(self):
|
def area(self):
|
||||||
""" Return the area. """
|
""" Return the area. """
|
||||||
return self.height()*self.width()
|
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)
|
||||||
|
|
||||||
def width(self):
|
def width(self):
|
||||||
""" Return width. Abs is for pre-normalized value."""
|
""" Return width. Abs is for pre-normalized value."""
|
||||||
return abs(self.rect[1].x-self.rect[0].x)
|
return abs(self.rect[1].x-self.rect[0].x)
|
||||||
|
|
||||||
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.rect
|
(first, second) = self.rect
|
||||||
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
|
ll = vector(min(first[0], second[0]), min(first[1], second[1]))
|
||||||
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
|
ur = vector(max(first[0], second[0]), max(first[1], second[1]))
|
||||||
self.rect=[ll,ur]
|
self.rect=[ll, ur]
|
||||||
|
|
||||||
def transform(self,offset,mirror,rotate):
|
|
||||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
|
||||||
We must then re-find the ll and ur. The master is the cell instance. """
|
|
||||||
(ll,ur) = self.rect
|
|
||||||
if mirror=="MX":
|
|
||||||
ll=ll.scale(1,-1)
|
|
||||||
ur=ur.scale(1,-1)
|
|
||||||
elif mirror=="MY":
|
|
||||||
ll=ll.scale(-1,1)
|
|
||||||
ur=ur.scale(-1,1)
|
|
||||||
elif mirror=="XY":
|
|
||||||
ll=ll.scale(-1,-1)
|
|
||||||
ur=ur.scale(-1,-1)
|
|
||||||
|
|
||||||
if rotate==90:
|
|
||||||
ll=ll.rotate_scale(-1,1)
|
|
||||||
ur=ur.rotate_scale(-1,1)
|
|
||||||
elif rotate==180:
|
|
||||||
ll=ll.scale(-1,-1)
|
|
||||||
ur=ur.scale(-1,-1)
|
|
||||||
elif rotate==270:
|
|
||||||
ll=ll.rotate_scale(1,-1)
|
|
||||||
ur=ur.rotate_scale(1,-1)
|
|
||||||
|
|
||||||
self.rect=[offset+ll,offset+ur]
|
def transform(self, offset, mirror, rotate):
|
||||||
|
"""
|
||||||
|
Transform with offset, mirror and rotation
|
||||||
|
to get the absolute pin location.
|
||||||
|
We must then re-find the ll and ur.
|
||||||
|
The master is the cell instance.
|
||||||
|
"""
|
||||||
|
(ll, ur) = self.rect
|
||||||
|
if mirror == "MX":
|
||||||
|
ll = ll.scale(1, -1)
|
||||||
|
ur = ur.scale(1, -1)
|
||||||
|
elif mirror == "MY":
|
||||||
|
ll = ll.scale(-1, 1)
|
||||||
|
ur = ur.scale(-1, 1)
|
||||||
|
elif mirror == "XY":
|
||||||
|
ll = ll.scale(-1, -1)
|
||||||
|
ur = ur.scale(-1, -1)
|
||||||
|
|
||||||
|
if rotate == 90:
|
||||||
|
ll = ll.rotate_scale(-1, 1)
|
||||||
|
ur = ur.rotate_scale(-1, 1)
|
||||||
|
elif rotate == 180:
|
||||||
|
ll = ll.scale(-1, -1)
|
||||||
|
ur = ur.scale(-1, -1)
|
||||||
|
elif rotate == 270:
|
||||||
|
ll = ll.rotate_scale(1, -1)
|
||||||
|
ur = ur.rotate_scale(1, -1)
|
||||||
|
|
||||||
|
self.rect = [offset + ll, offset + ur]
|
||||||
self.normalize()
|
self.normalize()
|
||||||
|
|
||||||
def center(self):
|
def center(self):
|
||||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),0.5*(self.rect[0].y+self.rect[1].y))
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||||
|
0.5*(self.rect[0].y+self.rect[1].y))
|
||||||
|
|
||||||
def cx(self):
|
def cx(self):
|
||||||
""" Center x """
|
""" Center x """
|
||||||
|
|
@ -250,7 +306,7 @@ class pin_layout:
|
||||||
def cy(self):
|
def cy(self):
|
||||||
""" Center y """
|
""" Center y """
|
||||||
return 0.5*(self.rect[0].y+self.rect[1].y)
|
return 0.5*(self.rect[0].y+self.rect[1].y)
|
||||||
|
|
||||||
# The four possible corners
|
# The four possible corners
|
||||||
def ll(self):
|
def ll(self):
|
||||||
""" Lower left point """
|
""" Lower left point """
|
||||||
|
|
@ -258,17 +314,17 @@ class pin_layout:
|
||||||
|
|
||||||
def ul(self):
|
def ul(self):
|
||||||
""" Upper left point """
|
""" Upper left point """
|
||||||
return vector(self.rect[0].x,self.rect[1].y)
|
return vector(self.rect[0].x, self.rect[1].y)
|
||||||
|
|
||||||
def lr(self):
|
def lr(self):
|
||||||
""" Lower right point """
|
""" Lower right point """
|
||||||
return vector(self.rect[1].x,self.rect[0].y)
|
return vector(self.rect[1].x, self.rect[0].y)
|
||||||
|
|
||||||
def ur(self):
|
def ur(self):
|
||||||
""" Upper right point """
|
""" Upper right point """
|
||||||
return self.rect[1]
|
return self.rect[1]
|
||||||
|
|
||||||
# The possible y edge values
|
# The possible y edge values
|
||||||
def uy(self):
|
def uy(self):
|
||||||
""" Upper y value """
|
""" Upper y value """
|
||||||
return self.rect[1].y
|
return self.rect[1].y
|
||||||
|
|
@ -278,80 +334,93 @@ class pin_layout:
|
||||||
return self.rect[0].y
|
return self.rect[0].y
|
||||||
|
|
||||||
# The possible x edge values
|
# The possible x edge values
|
||||||
|
|
||||||
def lx(self):
|
def lx(self):
|
||||||
""" Left x value """
|
""" Left x value """
|
||||||
return self.rect[0].x
|
return self.rect[0].x
|
||||||
|
|
||||||
def rx(self):
|
def rx(self):
|
||||||
""" Right x value """
|
""" Right x value """
|
||||||
return self.rect[1].x
|
return self.rect[1].x
|
||||||
|
|
||||||
|
|
||||||
# The edge centers
|
# The edge centers
|
||||||
def rc(self):
|
def rc(self):
|
||||||
""" Right center point """
|
""" Right center point """
|
||||||
return vector(self.rect[1].x,0.5*(self.rect[0].y+self.rect[1].y))
|
return vector(self.rect[1].x,
|
||||||
|
0.5*(self.rect[0].y+self.rect[1].y))
|
||||||
|
|
||||||
def lc(self):
|
def lc(self):
|
||||||
""" Left center point """
|
""" Left center point """
|
||||||
return vector(self.rect[0].x,0.5*(self.rect[0].y+self.rect[1].y))
|
return vector(self.rect[0].x,
|
||||||
|
0.5*(self.rect[0].y+self.rect[1].y))
|
||||||
|
|
||||||
def uc(self):
|
def uc(self):
|
||||||
""" Upper center point """
|
""" Upper center point """
|
||||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[1].y)
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||||
|
self.rect[1].y)
|
||||||
|
|
||||||
def bc(self):
|
def bc(self):
|
||||||
""" Bottom center point """
|
""" Bottom center point """
|
||||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[0].y)
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||||
|
self.rect[0].y)
|
||||||
|
|
||||||
def gds_write_file(self, newLayout):
|
def gds_write_file(self, newLayout):
|
||||||
"""Writes the pin shape and label to GDS"""
|
"""Writes the pin shape and label to GDS"""
|
||||||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||||
+ str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll()))
|
+ str(self.width()) + "x"
|
||||||
newLayout.addBox(layerNumber=layer[self.layer],
|
+ str(self.height()) + " @ " + str(self.ll()))
|
||||||
purposeNumber=0,
|
(layer_num, purpose) = layer[self.layer]
|
||||||
|
try:
|
||||||
|
from tech import pin_purpose
|
||||||
|
except ImportError:
|
||||||
|
pin_purpose = purpose
|
||||||
|
try:
|
||||||
|
from tech import label_purpose
|
||||||
|
except ImportError:
|
||||||
|
label_purpose = purpose
|
||||||
|
|
||||||
|
newLayout.addBox(layerNumber=layer_num,
|
||||||
|
purposeNumber=pin_purpose,
|
||||||
offsetInMicrons=self.ll(),
|
offsetInMicrons=self.ll(),
|
||||||
width=self.width(),
|
width=self.width(),
|
||||||
height=self.height(),
|
height=self.height(),
|
||||||
center=False)
|
center=False)
|
||||||
# Add the tet in the middle of the pin.
|
# Add the tet in the middle of the pin.
|
||||||
# This fixes some pin label offsetting when GDS gets imported into Magic.
|
# This fixes some pin label offsetting when GDS gets
|
||||||
|
# imported into Magic.
|
||||||
newLayout.addText(text=self.name,
|
newLayout.addText(text=self.name,
|
||||||
layerNumber=layer[self.layer],
|
layerNumber=layer_num,
|
||||||
purposeNumber=0,
|
purposeNumber=label_purpose,
|
||||||
offsetInMicrons=self.center(),
|
offsetInMicrons=self.center(),
|
||||||
magnification=GDS["zoom"],
|
magnification=GDS["zoom"],
|
||||||
rotate=None)
|
rotate=None)
|
||||||
|
|
||||||
|
|
||||||
def compute_overlap(self, other):
|
def compute_overlap(self, other):
|
||||||
""" Calculate the rectangular overlap of two rectangles. """
|
""" Calculate the rectangular overlap of two rectangles. """
|
||||||
(r1_ll,r1_ur) = self.rect
|
(r1_ll, r1_ur) = self.rect
|
||||||
(r2_ll,r2_ur) = other.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_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||||
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
# ov_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)
|
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)
|
dx = min(r1_ur.x, r2_ur.x) - max(r1_ll.x, r2_ll.x)
|
||||||
|
|
||||||
if dx>=0 and dy>=0:
|
if dx >= 0 and dy >= 0:
|
||||||
return [dx,dy]
|
return [dx, dy]
|
||||||
else:
|
else:
|
||||||
return [0,0]
|
return [0, 0]
|
||||||
|
|
||||||
def distance(self, other):
|
def distance(self, other):
|
||||||
"""
|
"""
|
||||||
Calculate the distance to another pin layout.
|
Calculate the distance to another pin layout.
|
||||||
"""
|
"""
|
||||||
(r1_ll,r1_ur) = self.rect
|
(r1_ll, r1_ur) = self.rect
|
||||||
(r2_ll,r2_ur) = other.rect
|
(r2_ll, r2_ur) = other.rect
|
||||||
|
|
||||||
def dist(x1, y1, x2, y2):
|
def dist(x1, y1, x2, y2):
|
||||||
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
||||||
|
|
||||||
left = r2_ur.x < r1_ll.x
|
left = r2_ur.x < r1_ll.x
|
||||||
right = r1_ur.x < r2_ll.x
|
right = r1_ur.x < r2_ll.x
|
||||||
bottom = r2_ur.y < r1_ll.y
|
bottom = r2_ur.y < r1_ll.y
|
||||||
|
|
@ -368,7 +437,7 @@ class pin_layout:
|
||||||
elif left:
|
elif left:
|
||||||
return r1_ll.x - r2_ur.x
|
return r1_ll.x - r2_ur.x
|
||||||
elif right:
|
elif right:
|
||||||
return r2_ll.x - r1.ur.x
|
return r2_ll.x - r1_ur.x
|
||||||
elif bottom:
|
elif bottom:
|
||||||
return r1_ll.y - r2_ur.y
|
return r1_ll.y - r2_ur.y
|
||||||
elif top:
|
elif top:
|
||||||
|
|
@ -376,10 +445,9 @@ class pin_layout:
|
||||||
else:
|
else:
|
||||||
# rectangles intersect
|
# rectangles intersect
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def overlap_length(self, other):
|
def overlap_length(self, other):
|
||||||
"""
|
"""
|
||||||
Calculate the intersection segment and determine its length
|
Calculate the intersection segment and determine its length
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -391,21 +459,21 @@ class pin_layout:
|
||||||
intersections = self.compute_overlap_segment(other)
|
intersections = self.compute_overlap_segment(other)
|
||||||
# This is the common case where two pairs of edges overlap
|
# This is the common case where two pairs of edges overlap
|
||||||
# at two points, so just find the distance between those two points
|
# at two points, so just find the distance between those two points
|
||||||
if len(intersections)==2:
|
if len(intersections) == 2:
|
||||||
(p1,p2) = intersections
|
(p1, p2) = intersections
|
||||||
return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2))
|
return math.sqrt(pow(p1[0]-p2[0], 2) + pow(p1[1]-p2[1], 2))
|
||||||
else:
|
else:
|
||||||
# This is where we had a corner intersection or none
|
# This is where we had a corner intersection or none
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def compute_overlap_segment(self, other):
|
def compute_overlap_segment(self, other):
|
||||||
"""
|
"""
|
||||||
Calculate the intersection segment of two rectangles
|
Calculate the intersection segment of two rectangles
|
||||||
(if any)
|
(if any)
|
||||||
"""
|
"""
|
||||||
(r1_ll,r1_ur) = self.rect
|
(r1_ll, r1_ur) = self.rect
|
||||||
(r2_ll,r2_ur) = other.rect
|
(r2_ll, r2_ur) = other.rect
|
||||||
|
|
||||||
# The other corners besides ll and ur
|
# The other corners besides ll and ur
|
||||||
r1_ul = vector(r1_ll.x, r1_ur.y)
|
r1_ul = vector(r1_ll.x, r1_ur.y)
|
||||||
|
|
@ -414,23 +482,24 @@ class pin_layout:
|
||||||
r2_lr = vector(r2_ur.x, r2_ll.y)
|
r2_lr = vector(r2_ur.x, r2_ll.y)
|
||||||
|
|
||||||
from itertools import tee
|
from itertools import tee
|
||||||
|
|
||||||
def pairwise(iterable):
|
def pairwise(iterable):
|
||||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||||
a, b = tee(iterable)
|
a, b = tee(iterable)
|
||||||
next(b, None)
|
next(b, None)
|
||||||
return zip(a, b)
|
return zip(a, b)
|
||||||
|
|
||||||
# R1 edges CW
|
# R1 edges CW
|
||||||
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
||||||
r1_edges = []
|
r1_edges = []
|
||||||
for (p,q) in pairwise(r1_cw_points):
|
for (p, q) in pairwise(r1_cw_points):
|
||||||
r1_edges.append([p,q])
|
r1_edges.append([p, q])
|
||||||
|
|
||||||
# R2 edges CW
|
# R2 edges CW
|
||||||
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
||||||
r2_edges = []
|
r2_edges = []
|
||||||
for (p,q) in pairwise(r2_cw_points):
|
for (p, q) in pairwise(r2_cw_points):
|
||||||
r2_edges.append([p,q])
|
r2_edges.append([p, q])
|
||||||
|
|
||||||
# There are 4 edges on each rectangle
|
# There are 4 edges on each rectangle
|
||||||
# so just brute force check intersection of each
|
# so just brute force check intersection of each
|
||||||
|
|
@ -452,36 +521,46 @@ class pin_layout:
|
||||||
q.x >= min(p.x, r.x) and \
|
q.x >= min(p.x, r.x) and \
|
||||||
q.y <= max(p.y, r.y) and \
|
q.y <= max(p.y, r.y) and \
|
||||||
q.y >= min(p.y, r.y):
|
q.y >= min(p.y, r.y):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def segment_intersection(self, s1, s2):
|
def segment_intersection(self, s1, s2):
|
||||||
"""
|
"""
|
||||||
Determine the intersection point of two segments
|
Determine the intersection point of two segments
|
||||||
Return the a segment if they overlap.
|
Return the a segment if they overlap.
|
||||||
Return None if they don't.
|
Return None if they don't.
|
||||||
"""
|
"""
|
||||||
(a,b) = s1
|
(a, b) = s1
|
||||||
(c,d) = s2
|
(c, d) = s2
|
||||||
# Line AB represented as a1x + b1y = c1
|
# Line AB represented as a1x + b1y = c1
|
||||||
a1 = b.y - a.y
|
a1 = b.y - a.y
|
||||||
b1 = a.x - b.x
|
b1 = a.x - b.x
|
||||||
c1 = a1*a.x + b1*a.y
|
c1 = a1*a.x + b1*a.y
|
||||||
|
|
||||||
# Line CD represented as a2x + b2y = c2
|
# Line CD represented as a2x + b2y = c2
|
||||||
a2 = d.y - c.y
|
a2 = d.y - c.y
|
||||||
b2 = c.x - d.x
|
b2 = c.x - d.x
|
||||||
c2 = a2*c.x + b2*c.y
|
c2 = a2*c.x + b2*c.y
|
||||||
|
|
||||||
determinant = a1*b2 - a2*b1
|
determinant = a1*b2 - a2*b1
|
||||||
|
|
||||||
if determinant!=0:
|
if determinant != 0:
|
||||||
x = (b2*c1 - b1*c2)/determinant
|
x = (b2*c1 - b1*c2)/determinant
|
||||||
y = (a1*c2 - a2*c1)/determinant
|
y = (a1*c2 - a2*c1)/determinant
|
||||||
|
|
||||||
r = vector(x,y).snap_to_grid()
|
r = vector(x, y).snap_to_grid()
|
||||||
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def same_lpp(self, lpp1, lpp2):
|
||||||
|
"""
|
||||||
|
Check if the layers and purposes are the same.
|
||||||
|
Ignore if purpose is a None.
|
||||||
|
"""
|
||||||
|
if lpp1[1] == None or lpp2[1] == None:
|
||||||
|
return lpp1[0] == lpp2[0]
|
||||||
|
|
||||||
|
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import os
|
|
||||||
import gdsMill
|
import gdsMill
|
||||||
import tech
|
import tech
|
||||||
import math
|
import math
|
||||||
|
|
@ -16,6 +15,7 @@ from pin_layout import pin_layout
|
||||||
|
|
||||||
OPTS = globals.OPTS
|
OPTS = globals.OPTS
|
||||||
|
|
||||||
|
|
||||||
def ceil(decimal):
|
def ceil(decimal):
|
||||||
"""
|
"""
|
||||||
Performs a ceiling function on the decimal place specified by the DRC grid.
|
Performs a ceiling function on the decimal place specified by the DRC grid.
|
||||||
|
|
@ -23,29 +23,35 @@ def ceil(decimal):
|
||||||
grid = tech.drc["grid"]
|
grid = tech.drc["grid"]
|
||||||
return math.ceil(decimal * 1 / grid) / (1 / grid)
|
return math.ceil(decimal * 1 / grid) / (1 / grid)
|
||||||
|
|
||||||
|
|
||||||
def round_to_grid(number):
|
def round_to_grid(number):
|
||||||
"""
|
"""
|
||||||
Rounds an arbitrary number to the grid.
|
Rounds an arbitrary number to the grid.
|
||||||
"""
|
"""
|
||||||
grid = tech.drc["grid"]
|
grid = tech.drc["grid"]
|
||||||
# this gets the nearest integer value
|
# this gets the nearest integer value
|
||||||
number_grid = int(round(round((number / grid), 2), 0))
|
number_grid = int(round(round((number / grid), 2), 0))
|
||||||
number_off = number_grid * grid
|
number_off = number_grid * grid
|
||||||
return number_off
|
return number_off
|
||||||
|
|
||||||
|
|
||||||
def snap_to_grid(offset):
|
def snap_to_grid(offset):
|
||||||
"""
|
"""
|
||||||
Changes the coodrinate to match the grid settings
|
Changes the coodrinate to match the grid settings
|
||||||
"""
|
"""
|
||||||
return [round_to_grid(offset[0]),round_to_grid(offset[1])]
|
return [round_to_grid(offset[0]),
|
||||||
|
round_to_grid(offset[1])]
|
||||||
|
|
||||||
|
|
||||||
def pin_center(boundary):
|
def pin_center(boundary):
|
||||||
"""
|
"""
|
||||||
This returns the center of a pin shape in the vlsiLayout border format.
|
This returns the center of a pin shape in the vlsiLayout border format.
|
||||||
"""
|
"""
|
||||||
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
|
return [0.5 * (boundary[0] + boundary[2]),
|
||||||
|
0.5 * (boundary[1] + boundary[3])]
|
||||||
|
|
||||||
def auto_measure_libcell(pin_list, name, units, layer):
|
|
||||||
|
def auto_measure_libcell(pin_list, name, units, lpp):
|
||||||
"""
|
"""
|
||||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||||
Return these as a set of properties including the cell width/height too.
|
Return these as a set of properties including the cell width/height too.
|
||||||
|
|
@ -56,43 +62,44 @@ def auto_measure_libcell(pin_list, name, units, layer):
|
||||||
reader.loadFromFile(cell_gds)
|
reader.loadFromFile(cell_gds)
|
||||||
|
|
||||||
cell = {}
|
cell = {}
|
||||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
measure_result = cell_vlsi.getLayoutBorder(lpp[0])
|
||||||
if measure_result == None:
|
if measure_result:
|
||||||
measure_result = cell_vlsi.measureSize(name)
|
measure_result = cell_vlsi.measureSize(name)
|
||||||
[cell["width"], cell["height"]] = measure_result
|
[cell["width"], cell["height"]] = measure_result
|
||||||
|
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
(name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin))
|
(name, lpp, boundary) = cell_vlsi.getPinShapeByLabel(str(pin))
|
||||||
cell[str(pin)] = pin_center(boundary)
|
cell[str(pin)] = pin_center(boundary)
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
|
|
||||||
|
def get_gds_size(name, gds_filename, units, lpp):
|
||||||
def get_gds_size(name, gds_filename, units, layer):
|
|
||||||
"""
|
"""
|
||||||
Open a GDS file and return the size from either the
|
Open a GDS file and return the size from either the
|
||||||
bounding box or a border layer.
|
bounding box or a border layer.
|
||||||
"""
|
"""
|
||||||
debug.info(4,"Creating VLSI layout for {}".format(name))
|
debug.info(4, "Creating VLSI layout for {}".format(name))
|
||||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||||
reader.loadFromFile(gds_filename)
|
reader.loadFromFile(gds_filename)
|
||||||
|
|
||||||
cell = {}
|
measure_result = cell_vlsi.getLayoutBorder(lpp)
|
||||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
if not measure_result:
|
||||||
if measure_result == None:
|
debug.info(2, "Layout border failed. Trying to measure size for {}".format(name))
|
||||||
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
|
|
||||||
measure_result = cell_vlsi.measureSize(name)
|
measure_result = cell_vlsi.measureSize(name)
|
||||||
# returns width,height
|
# returns width,height
|
||||||
return measure_result
|
return measure_result
|
||||||
|
|
||||||
def get_libcell_size(name, units, layer):
|
|
||||||
|
def get_libcell_size(name, units, lpp):
|
||||||
"""
|
"""
|
||||||
Open a GDS file and return the library cell size from either the
|
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))
|
return(get_gds_size(name, cell_gds, units, lpp))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_gds_pins(pin_names, name, gds_filename, units):
|
def get_gds_pins(pin_names, name, gds_filename, units):
|
||||||
|
|
@ -106,20 +113,24 @@ def get_gds_pins(pin_names, name, gds_filename, units):
|
||||||
|
|
||||||
cell = {}
|
cell = {}
|
||||||
for pin_name in pin_names:
|
for pin_name in pin_names:
|
||||||
cell[str(pin_name)]=[]
|
cell[str(pin_name)] = []
|
||||||
pin_list=cell_vlsi.getPinShape(str(pin_name))
|
pin_list = cell_vlsi.getPinShape(str(pin_name))
|
||||||
for pin_shape in pin_list:
|
for pin_shape in pin_list:
|
||||||
(layer,boundary)=pin_shape
|
(lpp, boundary) = pin_shape
|
||||||
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
rect = [vector(boundary[0], boundary[1]),
|
||||||
# this is a list because other cells/designs may have must-connect pins
|
vector(boundary[2], boundary[3])]
|
||||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
|
# this is a list because other cells/designs
|
||||||
|
# may have must-connect pins
|
||||||
|
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp))
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
|
|
||||||
def get_libcell_pins(pin_list, name, units):
|
def get_libcell_pins(pin_list, name, units):
|
||||||
"""
|
"""
|
||||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
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"
|
||||||
return(get_gds_pins(pin_list, name, cell_gds, units))
|
return(get_gds_pins(pin_list, name, cell_gds, units))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ class vector():
|
||||||
else:
|
else:
|
||||||
self.x = float(x)
|
self.x = float(x)
|
||||||
self.y = float(y)
|
self.y = float(y)
|
||||||
|
self._hash = hash((self.x,self.y))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
""" override print function output """
|
""" override print function output """
|
||||||
|
|
@ -49,7 +50,7 @@ class vector():
|
||||||
else:
|
else:
|
||||||
self.x=float(value[0])
|
self.x=float(value[0])
|
||||||
self.y=float(value[1])
|
self.y=float(value[1])
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"""
|
"""
|
||||||
override getitem function
|
override getitem function
|
||||||
|
|
@ -97,7 +98,7 @@ class vector():
|
||||||
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
||||||
break things.
|
break things.
|
||||||
"""
|
"""
|
||||||
return hash((self.x,self.y))
|
return self._hash
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,27 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
from tech import drc
|
||||||
import debug
|
import contact
|
||||||
from wire_path import wire_path
|
from wire_path import wire_path
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
||||||
|
|
||||||
class wire(wire_path):
|
class wire(wire_path):
|
||||||
"""
|
"""
|
||||||
Object metal wire; given the layer type
|
Object metal wire; given the layer type
|
||||||
Add a wire of minimium metal width between a set of points.
|
Add a wire of minimium metal width between a set of points.
|
||||||
The points should be rectilinear to control the bend points. If
|
The points should be rectilinear to control the bend points. If
|
||||||
not, it will always go down first.
|
not, it will always go down first.
|
||||||
The points are the center of the wire.
|
The points are the center of the wire.
|
||||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||||
|
The widen option will avoid via-to-via spacing problems for really short segments
|
||||||
|
(added as an option so we can disable it in bus connections)
|
||||||
"""
|
"""
|
||||||
def __init__(self, obj, layer_stack, position_list):
|
def __init__(self, obj, layer_stack, position_list, widen_short_wires=True):
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.layer_stack = layer_stack
|
self.layer_stack = layer_stack
|
||||||
self.position_list = position_list
|
self.position_list = position_list
|
||||||
|
self.widen_short_wires = widen_short_wires
|
||||||
self.pins = [] # used for matching parm lengths
|
self.pins = [] # used for matching parm lengths
|
||||||
self.switch_pos_list = []
|
self.switch_pos_list = []
|
||||||
|
|
||||||
|
|
@ -36,6 +40,7 @@ class wire(wire_path):
|
||||||
# wires and wire_paths should not be offset to (0,0)
|
# wires and wire_paths should not be offset to (0,0)
|
||||||
|
|
||||||
def setup_layers(self):
|
def setup_layers(self):
|
||||||
|
|
||||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
||||||
self.via_layer_name = via_layer
|
self.via_layer_name = via_layer
|
||||||
|
|
||||||
|
|
@ -47,21 +52,49 @@ class wire(wire_path):
|
||||||
via_connect = factory.create(module_type="contact",
|
via_connect = factory.create(module_type="contact",
|
||||||
layer_stack=self.layer_stack,
|
layer_stack=self.layer_stack,
|
||||||
dimensions=(1, 1))
|
dimensions=(1, 1))
|
||||||
|
|
||||||
|
# This is used for short connections to avoid via-to-via spacing errors
|
||||||
|
self.vert_layer_contact_width = max(via_connect.second_layer_width,
|
||||||
|
via_connect.first_layer_width)
|
||||||
|
self.horiz_layer_contact_width = max(via_connect.second_layer_height,
|
||||||
|
via_connect.first_layer_height)
|
||||||
|
|
||||||
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
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]
|
||||||
|
self.pitch = self.compute_pitch(self.layer_stack)
|
||||||
|
|
||||||
|
def compute_pitch(self, layer_stack):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is contact direction independent pitch,
|
||||||
|
i.e. we take the maximum contact dimension
|
||||||
|
"""
|
||||||
|
(layer1, via, layer2) = layer_stack
|
||||||
|
|
||||||
|
if layer1 == "poly" or layer1 == "active":
|
||||||
|
contact1 = getattr(contact, layer1 + "_contact")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
contact1 = getattr(contact, layer1 + "_via")
|
||||||
|
except AttributeError:
|
||||||
|
contact1 = getattr(contact, layer2 + "_via")
|
||||||
|
max_contact = max(contact1.width, contact1.height)
|
||||||
|
|
||||||
|
layer1_space = drc("{0}_to_{0}".format(layer1))
|
||||||
|
layer2_space = drc("{0}_to_{0}".format(layer2))
|
||||||
|
pitch = max_contact + max(layer1_space, layer2_space)
|
||||||
|
|
||||||
|
return pitch
|
||||||
|
|
||||||
# create a 1x1 contact
|
# create a 1x1 contact
|
||||||
def create_vias(self):
|
def create_vias(self):
|
||||||
""" Add a via and corner square at every corner of the path."""
|
""" Add a via and corner square at every corner of the path."""
|
||||||
self.c=factory.create(module_type="contact",
|
self.c=factory.create(module_type="contact",
|
||||||
layer_stack=self.layer_stack,
|
layer_stack=self.layer_stack,
|
||||||
dimensions=(1, 1))
|
dimensions=(1, 1))
|
||||||
c_width = self.c.width
|
from itertools import tee, islice
|
||||||
c_height = self.c.height
|
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||||
|
threewise = nwise(self.position_list, 3)
|
||||||
from itertools import tee,islice
|
|
||||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
|
||||||
threewise=nwise(self.position_list,3)
|
|
||||||
|
|
||||||
for (a, offset, c) in list(threewise):
|
for (a, offset, c) in list(threewise):
|
||||||
# add a exceptions to prevent a via when we don't change directions
|
# add a exceptions to prevent a via when we don't change directions
|
||||||
|
|
@ -72,18 +105,25 @@ class wire(wire_path):
|
||||||
self.obj.add_via_center(layers=self.layer_stack,
|
self.obj.add_via_center(layers=self.layer_stack,
|
||||||
offset=offset)
|
offset=offset)
|
||||||
|
|
||||||
|
|
||||||
def create_rectangles(self):
|
def create_rectangles(self):
|
||||||
"""
|
"""
|
||||||
Create the actual rectangles on the appropriate layers
|
Create the actual rectangles on the appropriate layers
|
||||||
using the position list of the corners.
|
using the position list of the corners.
|
||||||
"""
|
"""
|
||||||
pl = self.position_list # position list
|
pl = self.position_list # position list
|
||||||
for index in range(len(pl) - 1):
|
for index in range(len(pl) - 1):
|
||||||
|
# Horizontal wire segment
|
||||||
if pl[index][0] != pl[index + 1][0]:
|
if pl[index][0] != pl[index + 1][0]:
|
||||||
line_length = pl[index + 1][0] - pl[index][0]
|
line_length = pl[index + 1][0] - pl[index][0]
|
||||||
|
# Make the wire wider to avoid via-to-via spacing problems
|
||||||
|
# But don't make it wider if it is shorter than one via
|
||||||
|
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
|
||||||
|
width = self.horiz_layer_contact_width
|
||||||
|
else:
|
||||||
|
width = self.horiz_layer_width
|
||||||
temp_offset = [pl[index][0],
|
temp_offset = [pl[index][0],
|
||||||
pl[index][1] - 0.5*self.horiz_layer_width]
|
pl[index][1] - 0.5 * width]
|
||||||
|
# If we go in the negative direction, move the offset
|
||||||
if line_length < 0:
|
if line_length < 0:
|
||||||
temp_offset = [temp_offset[0] + line_length,
|
temp_offset = [temp_offset[0] + line_length,
|
||||||
temp_offset[1]]
|
temp_offset[1]]
|
||||||
|
|
@ -91,10 +131,17 @@ class wire(wire_path):
|
||||||
length=abs(line_length),
|
length=abs(line_length),
|
||||||
offset=temp_offset,
|
offset=temp_offset,
|
||||||
orientation="horizontal",
|
orientation="horizontal",
|
||||||
layer_width=self.horiz_layer_width)
|
layer_width=width)
|
||||||
|
# Vertical wire segment
|
||||||
elif pl[index][1] != pl[index + 1][1]:
|
elif pl[index][1] != pl[index + 1][1]:
|
||||||
line_length = pl[index + 1][1] - pl[index][1]
|
line_length = pl[index + 1][1] - pl[index][1]
|
||||||
temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width,
|
# Make the wire wider to avoid via-to-via spacing problems
|
||||||
|
# But don't make it wider if it is shorter than one via
|
||||||
|
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
|
||||||
|
width = self.vert_layer_contact_width
|
||||||
|
else:
|
||||||
|
width = self.vert_layer_width
|
||||||
|
temp_offset = [pl[index][0] - 0.5 * width,
|
||||||
pl[index][1]]
|
pl[index][1]]
|
||||||
if line_length < 0:
|
if line_length < 0:
|
||||||
temp_offset = [temp_offset[0],
|
temp_offset = [temp_offset[0],
|
||||||
|
|
@ -103,11 +150,13 @@ class wire(wire_path):
|
||||||
length=abs(line_length),
|
length=abs(line_length),
|
||||||
offset=temp_offset,
|
offset=temp_offset,
|
||||||
orientation="vertical",
|
orientation="vertical",
|
||||||
layer_width=self.vert_layer_width)
|
layer_width=width)
|
||||||
|
|
||||||
def assert_node(self, A, B):
|
def assert_node(self, A, B):
|
||||||
""" Check if the node movements are not big enough for the
|
"""
|
||||||
technology sizes."""
|
Check if the node movements are not big enough for the
|
||||||
|
technology sizes.
|
||||||
|
"""
|
||||||
X_diff = abs(A[0] - B[0])
|
X_diff = abs(A[0] - B[0])
|
||||||
Y_diff = abs(A[1] - B[1])
|
Y_diff = abs(A[1] - B[1])
|
||||||
[minX, minY] = self.node_to_node
|
[minX, minY] = self.node_to_node
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@ def create_rectilinear_route(my_list):
|
||||||
my_list.append(vector(pl[index][0], pl[index + 1][1]))
|
my_list.append(vector(pl[index][0], pl[index + 1][1]))
|
||||||
my_list.append(vector(pl[-1]))
|
my_list.append(vector(pl[-1]))
|
||||||
return my_list
|
return my_list
|
||||||
|
|
||||||
|
|
||||||
class wire_path():
|
class wire_path():
|
||||||
"""
|
"""
|
||||||
Object metal wire_path; given the layer type
|
Object metal wire_path; given the layer type
|
||||||
Add a wire_path of minimium metal width between a set of points.
|
Add a wire_path of minimium metal width between a set of points.
|
||||||
The points should be rectilinear to control the bend points. If
|
The points should be rectilinear to control the bend points. If
|
||||||
not, it will always go down first. The points are the center of the wire_path.
|
not, it will always go down first. The points are the center of the wire_path.
|
||||||
If width is not given, it uses minimum layer width.
|
If width is not given, it uses minimum layer width.
|
||||||
|
|
@ -37,7 +38,7 @@ class wire_path():
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.layer_name = layer
|
self.layer_name = layer
|
||||||
self.layer_id = techlayer[layer]
|
self.layer_id = techlayer[layer]
|
||||||
if width==None:
|
if width == None:
|
||||||
self.layer_width = drc["minwidth_{0}".format(layer)]
|
self.layer_width = drc["minwidth_{0}".format(layer)]
|
||||||
else:
|
else:
|
||||||
self.layer_width = width
|
self.layer_width = width
|
||||||
|
|
@ -46,7 +47,6 @@ class wire_path():
|
||||||
self.switch_pos_list = []
|
self.switch_pos_list = []
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.create_rectilinear()
|
self.create_rectilinear()
|
||||||
self.connect_corner()
|
self.connect_corner()
|
||||||
|
|
@ -60,9 +60,9 @@ class wire_path():
|
||||||
|
|
||||||
def connect_corner(self):
|
def connect_corner(self):
|
||||||
""" Add a corner square at every corner of the wire_path."""
|
""" Add a corner square at every corner of the wire_path."""
|
||||||
from itertools import tee,islice
|
from itertools import tee, islice
|
||||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||||
threewise=nwise(self.position_list,3)
|
threewise=nwise(self.position_list, 3)
|
||||||
|
|
||||||
for (a, offset, c) in list(threewise):
|
for (a, offset, c) in list(threewise):
|
||||||
# add a exceptions to prevent a corner when we retrace back in the same direction
|
# add a exceptions to prevent a corner when we retrace back in the same direction
|
||||||
|
|
@ -74,7 +74,6 @@ class wire_path():
|
||||||
offset[1] - 0.5 * self.layer_width]
|
offset[1] - 0.5 * self.layer_width]
|
||||||
self.draw_corner_wire(corner_offset)
|
self.draw_corner_wire(corner_offset)
|
||||||
|
|
||||||
|
|
||||||
def draw_corner_wire(self, offset):
|
def draw_corner_wire(self, offset):
|
||||||
""" 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."""
|
||||||
|
|
@ -117,7 +116,7 @@ class wire_path():
|
||||||
|
|
||||||
def add_line(self, layer_name, length, offset, orientation, layer_width):
|
def add_line(self, layer_name, length, offset, orientation, layer_width):
|
||||||
"""
|
"""
|
||||||
straight line object with layer_minwidth
|
straight line object with layer_minwidth
|
||||||
(orientation: "vertical" or "horizontal") default is vertical
|
(orientation: "vertical" or "horizontal") default is vertical
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class bitcell(bitcell_base.bitcell_base):
|
class bitcell(bitcell_base.bitcell_base):
|
||||||
"""
|
"""
|
||||||
|
|
@ -18,10 +19,22 @@ class bitcell(bitcell_base.bitcell_base):
|
||||||
the layout and netlist should be available in the technology
|
the layout and netlist should be available in the technology
|
||||||
library.
|
library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
# If we have a split WL bitcell, if not be backwards
|
||||||
storage_nets = ['Q', 'Q_bar']
|
# compatible in the tech file
|
||||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
|
||||||
|
if props.bitcell.split_wl:
|
||||||
|
pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"]
|
||||||
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
|
else:
|
||||||
|
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||||
|
props.bitcell.cell_6t.pin.br,
|
||||||
|
props.bitcell.cell_6t.pin.wl,
|
||||||
|
props.bitcell.cell_6t.pin.vdd,
|
||||||
|
props.bitcell.cell_6t.pin.gnd]
|
||||||
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||||
|
storage_nets = ['Q', 'Qbar']
|
||||||
|
|
||||||
(width, height) = utils.get_libcell_size("cell_6t",
|
(width, height) = utils.get_libcell_size("cell_6t",
|
||||||
GDS["unit"],
|
GDS["unit"],
|
||||||
layer["boundary"])
|
layer["boundary"])
|
||||||
|
|
@ -37,42 +50,49 @@ class bitcell(bitcell_base.bitcell_base):
|
||||||
self.pin_map = bitcell.pin_map
|
self.pin_map = bitcell.pin_map
|
||||||
self.add_pin_types(self.type_list)
|
self.add_pin_types(self.type_list)
|
||||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||||
|
|
||||||
|
debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells")
|
||||||
|
|
||||||
def get_all_wl_names(self):
|
def get_all_wl_names(self):
|
||||||
""" Creates a list of all wordline pin names """
|
""" Creates a list of all wordline pin names """
|
||||||
row_pins = ["wl"]
|
if props.bitcell.split_wl:
|
||||||
|
row_pins = ["wl0", "wl1"]
|
||||||
|
else:
|
||||||
|
row_pins = [props.bitcell.cell_6t.pin.wl]
|
||||||
return row_pins
|
return row_pins
|
||||||
|
|
||||||
def get_all_bitline_names(self):
|
def get_all_bitline_names(self):
|
||||||
""" Creates a list of all bitline pin names (both bl and br) """
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
column_pins = ["bl", "br"]
|
pin = props.bitcell.cell_6t.pin
|
||||||
|
column_pins = [pin.bl, pin.br]
|
||||||
return column_pins
|
return column_pins
|
||||||
|
|
||||||
def get_all_bl_names(self):
|
def get_all_bl_names(self):
|
||||||
""" Creates a list of all bl pins names """
|
""" Creates a list of all bl pins names """
|
||||||
column_pins = ["bl"]
|
return [props.bitcell.cell_6t.pin.bl]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_all_br_names(self):
|
def get_all_br_names(self):
|
||||||
""" Creates a list of all br pins names """
|
""" Creates a list of all br pins names """
|
||||||
column_pins = ["br"]
|
return [props.bitcell.cell_6t.pin.br]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_bl_name(self, port=0):
|
def get_bl_name(self, port=0):
|
||||||
"""Get bl name"""
|
"""Get bl name"""
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
return "bl"
|
return props.bitcell.cell_6t.pin.bl
|
||||||
|
|
||||||
def get_br_name(self, port=0):
|
def get_br_name(self, port=0):
|
||||||
"""Get bl name"""
|
"""Get bl name"""
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
return "br"
|
return props.bitcell.cell_6t.pin.br
|
||||||
|
|
||||||
def get_wl_name(self, port=0):
|
def get_wl_name(self, port=0):
|
||||||
"""Get wl name"""
|
"""Get wl name"""
|
||||||
debug.check(port == 0, "One port for bitcell only.")
|
if props.bitcell.split_wl:
|
||||||
return "wl"
|
return "wl{}".format(port)
|
||||||
|
else:
|
||||||
|
debug.check(port == 0, "One port for bitcell only.")
|
||||||
|
return props.bitcell.cell_6t.pin.wl
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""
|
"""
|
||||||
Adds edges based on inputs/outputs.
|
Adds edges based on inputs/outputs.
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer, parameter, drc
|
from tech import GDS, layer, parameter, drc
|
||||||
|
from tech import cell_properties as props
|
||||||
import logical_effort
|
import logical_effort
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|
@ -20,7 +21,15 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base):
|
||||||
library.
|
library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1rw1r.pin.gnd]
|
||||||
|
|
||||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
||||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
storage_nets = ['Q', 'Q_bar']
|
storage_nets = ['Q', 'Q_bar']
|
||||||
|
|
@ -39,85 +48,92 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base):
|
||||||
self.pin_map = bitcell_1rw_1r.pin_map
|
self.pin_map = bitcell_1rw_1r.pin_map
|
||||||
self.add_pin_types(self.type_list)
|
self.add_pin_types(self.type_list)
|
||||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||||
|
|
||||||
|
pin_names = bitcell_1rw_1r.pin_names
|
||||||
|
self.bl_names = [pin_names[0], pin_names[2]]
|
||||||
|
self.br_names = [pin_names[1], pin_names[3]]
|
||||||
|
self.wl_names = [pin_names[4], pin_names[5]]
|
||||||
|
|
||||||
def get_bitcell_pins(self, col, row):
|
def get_bitcell_pins(self, col, row):
|
||||||
"""
|
"""
|
||||||
Creates a list of connections in the bitcell,
|
Creates a list of connections in the bitcell,
|
||||||
indexed by column and row, for instance use in bitcell_array
|
indexed by column and row, for instance use in bitcell_array
|
||||||
"""
|
"""
|
||||||
bitcell_pins = ["bl0_{0}".format(col),
|
pin_name = props.bitcell.cell_1rw1r.pin
|
||||||
"br0_{0}".format(col),
|
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
|
||||||
"bl1_{0}".format(col),
|
"{0}_{1}".format(pin_name.br0, col),
|
||||||
"br1_{0}".format(col),
|
"{0}_{1}".format(pin_name.bl1, col),
|
||||||
"wl0_{0}".format(row),
|
"{0}_{1}".format(pin_name.br1, col),
|
||||||
"wl1_{0}".format(row),
|
"{0}_{1}".format(pin_name.wl0, row),
|
||||||
|
"{0}_{1}".format(pin_name.wl1, row),
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"]
|
"gnd"]
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
||||||
def get_all_wl_names(self):
|
def get_all_wl_names(self):
|
||||||
""" Creates a list of all wordline pin names """
|
""" Creates a list of all wordline pin names """
|
||||||
row_pins = ["wl0", "wl1"]
|
return [props.bitcell.cell_1rw1r.pin.wl0,
|
||||||
return row_pins
|
props.bitcell.cell_1rw1r.pin.wl1]
|
||||||
|
|
||||||
def get_all_bitline_names(self):
|
def get_all_bitline_names(self):
|
||||||
""" Creates a list of all bitline pin names (both bl and br) """
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
column_pins = ["bl0", "br0", "bl1", "br1"]
|
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1rw1r.pin.br0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br1]
|
||||||
|
|
||||||
def get_all_bl_names(self):
|
def get_all_bl_names(self):
|
||||||
""" Creates a list of all bl pins names """
|
""" Creates a list of all bl pins names """
|
||||||
column_pins = ["bl0", "bl1"]
|
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1rw1r.pin.bl1]
|
||||||
|
|
||||||
def get_all_br_names(self):
|
def get_all_br_names(self):
|
||||||
""" Creates a list of all br pins names """
|
""" Creates a list of all br pins names """
|
||||||
column_pins = ["br0", "br1"]
|
return [props.bitcell.cell_1rw1r.pin.br0,
|
||||||
return column_pins
|
props.bitcell.cell_1rw1r.pin.br1]
|
||||||
|
|
||||||
def get_read_bl_names(self):
|
def get_read_bl_names(self):
|
||||||
""" Creates a list of bl pin names associated with read ports """
|
""" Creates a list of bl pin names associated with read ports """
|
||||||
column_pins = ["bl0", "bl1"]
|
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1rw1r.pin.bl1]
|
||||||
|
|
||||||
def get_read_br_names(self):
|
def get_read_br_names(self):
|
||||||
""" Creates a list of br pin names associated with read ports """
|
""" Creates a list of br pin names associated with read ports """
|
||||||
column_pins = ["br0", "br1"]
|
return [props.bitcell.cell_1rw1r.pin.br0,
|
||||||
return column_pins
|
props.bitcell.cell_1rw1r.pin.br1]
|
||||||
|
|
||||||
def get_write_bl_names(self):
|
def get_write_bl_names(self):
|
||||||
""" Creates a list of bl pin names associated with write ports """
|
""" Creates a list of bl pin names associated with write ports """
|
||||||
column_pins = ["bl0"]
|
return [props.bitcell.cell_1rw1r.pin.bl0]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_write_br_names(self):
|
def get_write_br_names(self):
|
||||||
""" Creates a list of br pin names asscociated with write ports"""
|
""" Creates a list of br pin names asscociated with write ports"""
|
||||||
column_pins = ["br0"]
|
return [props.bitcell.cell_1rw1r.pin.br1]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_bl_name(self, port=0):
|
def get_bl_name(self, port=0):
|
||||||
"""Get bl name by port"""
|
"""Get bl name by port"""
|
||||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||||
return "bl{}".format(port)
|
return self.bl_names[port]
|
||||||
|
|
||||||
def get_br_name(self, port=0):
|
def get_br_name(self, port=0):
|
||||||
"""Get bl name by port"""
|
"""Get bl name by port"""
|
||||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||||
return "br{}".format(port)
|
return self.br_names[port]
|
||||||
|
|
||||||
def get_wl_name(self, port=0):
|
def get_wl_name(self, port=0):
|
||||||
"""Get wl name by port"""
|
"""Get wl name by port"""
|
||||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||||
return "wl{}".format(port)
|
return self.wl_names[port]
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||||
to use the add_graph_edges function."""
|
to use the add_graph_edges function."""
|
||||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||||
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||||
# Port 0 edges
|
# Port 0 edges
|
||||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
|
pins = props.bitcell.cell_1rw1r.pin
|
||||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
|
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self)
|
||||||
|
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self)
|
||||||
# Port 1 edges
|
# Port 1 edges
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,7 +20,14 @@ class bitcell_1w_1r(bitcell_base.bitcell_base):
|
||||||
library.
|
library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.br0,
|
||||||
|
props.bitcell.cell_1w1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.br1,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1w1r.pin.gnd]
|
||||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
||||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
storage_nets = ['Q', 'Q_bar']
|
storage_nets = ['Q', 'Q_bar']
|
||||||
|
|
@ -39,80 +47,88 @@ class bitcell_1w_1r(bitcell_base.bitcell_base):
|
||||||
self.add_pin_types(self.type_list)
|
self.add_pin_types(self.type_list)
|
||||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||||
|
|
||||||
|
pin_names = bitcell_1w_1r.pin_names
|
||||||
|
self.bl_names = [pin_names[0], pin_names[2]]
|
||||||
|
self.br_names = [pin_names[1], pin_names[3]]
|
||||||
|
self.wl_names = [pin_names[4], pin_names[5]]
|
||||||
|
|
||||||
|
|
||||||
def get_bitcell_pins(self, col, row):
|
def get_bitcell_pins(self, col, row):
|
||||||
"""
|
"""
|
||||||
Creates a list of connections in the bitcell,
|
Creates a list of connections in the bitcell,
|
||||||
indexed by column and row, for instance use in bitcell_array
|
indexed by column and row, for instance use in bitcell_array
|
||||||
"""
|
"""
|
||||||
bitcell_pins = ["bl0_{0}".format(col),
|
pin_name = props.bitcell.cell_1w1r.pin
|
||||||
"br0_{0}".format(col),
|
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
|
||||||
"bl1_{0}".format(col),
|
"{0}_{1}".format(pin_name.br0, col),
|
||||||
"br1_{0}".format(col),
|
"{0}_{1}".format(pin_name.bl1, col),
|
||||||
"wl0_{0}".format(row),
|
"{0}_{1}".format(pin_name.br1, col),
|
||||||
"wl1_{0}".format(row),
|
"{0}_{1}".format(pin_name.wl0, row),
|
||||||
|
"{0}_{1}".format(pin_name.wl1, row),
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"]
|
"gnd"]
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
||||||
def get_all_wl_names(self):
|
def get_all_wl_names(self):
|
||||||
""" Creates a list of all wordline pin names """
|
""" Creates a list of all wordline pin names """
|
||||||
row_pins = ["wl0", "wl1"]
|
return [props.bitcell.cell_1w1r.pin.wl0,
|
||||||
return row_pins
|
props.bitcell.cell_1w1r.pin.wl1]
|
||||||
|
|
||||||
def get_all_bitline_names(self):
|
def get_all_bitline_names(self):
|
||||||
""" Creates a list of all bitline pin names (both bl and br) """
|
""" Creates a list of all bitline pin names (both bl and br) """
|
||||||
column_pins = ["bl0", "br0", "bl1", "br1"]
|
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1w1r.pin.br0,
|
||||||
|
props.bitcell.cell_1w1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.br1]
|
||||||
|
|
||||||
def get_all_bl_names(self):
|
def get_all_bl_names(self):
|
||||||
""" Creates a list of all bl pins names """
|
""" Creates a list of all bl pins names """
|
||||||
column_pins = ["bl0", "bl1"]
|
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1w1r.pin.bl1]
|
||||||
|
|
||||||
def get_all_br_names(self):
|
def get_all_br_names(self):
|
||||||
""" Creates a list of all br pins names """
|
""" Creates a list of all br pins names """
|
||||||
column_pins = ["br0", "br1"]
|
return [props.bitcell.cell_1w1r.pin.br0,
|
||||||
return column_pins
|
props.bitcell.cell_1w1r.pin.br1]
|
||||||
|
|
||||||
def get_read_bl_names(self):
|
def get_read_bl_names(self):
|
||||||
""" Creates a list of bl pin names associated with read ports """
|
""" Creates a list of bl pin names associated with read ports """
|
||||||
column_pins = ["bl0", "bl1"]
|
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
return column_pins
|
props.bitcell.cell_1w1r.pin.bl1]
|
||||||
|
|
||||||
def get_read_br_names(self):
|
def get_read_br_names(self):
|
||||||
""" Creates a list of br pin names associated with read ports """
|
""" Creates a list of br pin names associated with read ports """
|
||||||
column_pins = ["br0", "br1"]
|
return [props.bitcell.cell_1w1r.pin.br0,
|
||||||
return column_pins
|
props.bitcell.cell_1w1r.pin.br1]
|
||||||
|
|
||||||
def get_write_bl_names(self):
|
def get_write_bl_names(self):
|
||||||
""" Creates a list of bl pin names associated with write ports """
|
""" Creates a list of bl pin names associated with write ports """
|
||||||
column_pins = ["bl0"]
|
return [props.bitcell.cell_1w1r.pin.bl0]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_write_br_names(self):
|
def get_write_br_names(self):
|
||||||
""" Creates a list of br pin names asscociated with write ports"""
|
""" Creates a list of br pin names asscociated with write ports"""
|
||||||
column_pins = ["br0"]
|
return [props.bitcell.cell_1w1r.pin.br1]
|
||||||
return column_pins
|
|
||||||
|
|
||||||
def get_bl_name(self, port=0):
|
def get_bl_name(self, port=0):
|
||||||
"""Get bl name by port"""
|
"""Get bl name by port"""
|
||||||
return "bl{}".format(port)
|
return self.bl_names[port]
|
||||||
|
|
||||||
def get_br_name(self, port=0):
|
def get_br_name(self, port=0):
|
||||||
"""Get bl name by port"""
|
"""Get bl name by port"""
|
||||||
return "br{}".format(port)
|
return self.br_names[port]
|
||||||
|
|
||||||
def get_wl_name(self, port=0):
|
def get_wl_name(self, port=0):
|
||||||
"""Get wl name by port"""
|
"""Get wl name by port"""
|
||||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||||
return "wl{}".format(port)
|
return self.wl_names[port]
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||||
to use the add_graph_edges function."""
|
to use the add_graph_edges function."""
|
||||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||||
|
pins = props.bitcell.cell_1w1r.pin
|
||||||
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||||
# Port 0 edges
|
# Port 0 edges
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||||
# Port 1 is a write port, so its timing is not considered here.
|
# Port 1 is a write port, so its timing is not considered here.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
import utils
|
||||||
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
|
||||||
|
"""
|
||||||
|
todo"""
|
||||||
|
|
||||||
|
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.vdd]
|
||||||
|
|
||||||
|
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
||||||
|
"POWER", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names,
|
||||||
|
"col_cap_cell_1rw_1r",
|
||||||
|
GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name=""):
|
||||||
|
# Ignore the name argument
|
||||||
|
bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r")
|
||||||
|
debug.info(2, "Create col_cap bitcell 1rw+1r object")
|
||||||
|
|
||||||
|
self.width = col_cap_bitcell_1rw_1r.width
|
||||||
|
self.height = col_cap_bitcell_1rw_1r.height
|
||||||
|
self.pin_map = col_cap_bitcell_1rw_1r.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
self.no_instances = True
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,8 +19,12 @@ class dummy_bitcell(bitcell_base.bitcell_base):
|
||||||
the layout and netlist should be available in the technology
|
the layout and netlist should be available in the technology
|
||||||
library.
|
library.
|
||||||
"""
|
"""
|
||||||
|
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||||
|
props.bitcell.cell_6t.pin.br,
|
||||||
|
props.bitcell.cell_6t.pin.wl,
|
||||||
|
props.bitcell.cell_6t.pin.vdd,
|
||||||
|
props.bitcell.cell_6t.pin.gnd]
|
||||||
|
|
||||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
|
||||||
(width, height) = utils.get_libcell_size("dummy_cell_6t",
|
(width, height) = utils.get_libcell_size("dummy_cell_6t",
|
||||||
GDS["unit"],
|
GDS["unit"],
|
||||||
layer["boundary"])
|
layer["boundary"])
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,7 +19,15 @@ class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
the technology library. """
|
the technology library. """
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1rw1r.pin.gnd]
|
||||||
|
|
||||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
||||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width, height) = utils.get_libcell_size("dummy_cell_1rw_1r",
|
(width, height) = utils.get_libcell_size("dummy_cell_1rw_1r",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS, layer
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
import bitcell_base
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,7 +19,14 @@ class dummy_bitcell_1w_1r(bitcell_base.bitcell_base):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
the technology library. """
|
the technology library. """
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.br0,
|
||||||
|
props.bitcell.cell_1w1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.br1,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1w1r.pin.gnd]
|
||||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
||||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width, height) = utils.get_libcell_size("dummy_cell_1w_1r",
|
(width, height) = utils.get_libcell_size("dummy_cell_1w_1r",
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class dummy_pbitcell(design.design):
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
self.add_boundary()
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
#
|
#
|
||||||
import contact
|
import contact
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, parameter
|
from tech import drc, parameter, layer
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from ptx import ptx
|
from ptx import ptx
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
@ -199,7 +199,6 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
def calculate_spacing(self):
|
def calculate_spacing(self):
|
||||||
""" Calculate transistor spacings """
|
""" Calculate transistor spacings """
|
||||||
|
|
||||||
# calculate metal contact extensions over transistor active
|
# calculate metal contact extensions over transistor active
|
||||||
readwrite_nmos_contact_extension = 0.5 * \
|
readwrite_nmos_contact_extension = 0.5 * \
|
||||||
(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
||||||
|
|
@ -213,20 +212,20 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# y-offset for the access transistor's gate contact
|
# y-offset for the access transistor's gate contact
|
||||||
self.gate_contact_yoffset = max_contact_extension + self.m2_space \
|
self.gate_contact_yoffset = max_contact_extension + self.m2_space \
|
||||||
+ 0.5 * max(contact.poly.height, contact.m1m2.height)
|
+ 0.5 * max(contact.poly_contact.height, contact.m1_via.height)
|
||||||
|
|
||||||
# y-position of access transistors
|
# y-position of access transistors
|
||||||
self.port_ypos = self.m1_space + 0.5 * contact.m1m2.height + self.gate_contact_yoffset
|
self.port_ypos = self.m1_space + 0.5 * contact.m1_via.height + self.gate_contact_yoffset
|
||||||
|
|
||||||
# y-position of inverter nmos
|
# y-position of inverter nmos
|
||||||
self.inverter_nmos_ypos = self.port_ypos
|
self.inverter_nmos_ypos = self.port_ypos
|
||||||
|
|
||||||
# spacing between ports (same for read/write and write ports)
|
# spacing between ports (same for read/write and write ports)
|
||||||
self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \
|
self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \
|
||||||
+ 0.5 * contact.m1m2.height \
|
+ 0.5 * contact.m1_via.height \
|
||||||
+ self.m2_space + self.m2_width
|
+ self.m2_space + self.m2_width
|
||||||
m2_constraint = self.bitline_offset + self.m2_space \
|
m2_constraint = self.bitline_offset + self.m2_space \
|
||||||
+ 0.5 * contact.m1m2.height \
|
+ 0.5 * contact.m1_via.height \
|
||||||
- 0.5 * self.readwrite_nmos.active_width
|
- 0.5 * self.readwrite_nmos.active_width
|
||||||
self.write_port_spacing = max(self.active_space,
|
self.write_port_spacing = max(self.active_space,
|
||||||
self.m1_space,
|
self.m1_space,
|
||||||
|
|
@ -234,7 +233,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
self.read_port_spacing = self.bitline_offset + self.m2_space
|
self.read_port_spacing = self.bitline_offset + self.m2_space
|
||||||
|
|
||||||
# spacing between cross coupled inverters
|
# spacing between cross coupled inverters
|
||||||
self.inverter_to_inverter_spacing = contact.poly.width + self.m1_space
|
self.inverter_to_inverter_spacing = contact.poly_contact.width + self.m1_space
|
||||||
|
|
||||||
# calculations related to inverter connections
|
# calculations related to inverter connections
|
||||||
inverter_pmos_contact_extension = 0.5 * \
|
inverter_pmos_contact_extension = 0.5 * \
|
||||||
|
|
@ -243,19 +242,19 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
|
(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
|
||||||
self.inverter_gap = max(self.poly_to_active,
|
self.inverter_gap = max(self.poly_to_active,
|
||||||
self.m1_space + inverter_nmos_contact_extension) \
|
self.m1_space + inverter_nmos_contact_extension) \
|
||||||
+ self.poly_to_polycontact + 2 * contact.poly.width \
|
+ self.poly_to_contact + 2 * contact.poly_contact.width \
|
||||||
+ self.m1_space + inverter_pmos_contact_extension
|
+ self.m1_space + inverter_pmos_contact_extension
|
||||||
self.cross_couple_lower_ypos = self.inverter_nmos_ypos \
|
self.cross_couple_lower_ypos = self.inverter_nmos_ypos \
|
||||||
+ self.inverter_nmos.active_height \
|
+ self.inverter_nmos.active_height \
|
||||||
+ max(self.poly_to_active,
|
+ max(self.poly_to_active,
|
||||||
self.m1_space + inverter_nmos_contact_extension) \
|
self.m1_space + inverter_nmos_contact_extension) \
|
||||||
+ 0.5 * contact.poly.width
|
+ 0.5 * contact.poly_contact.width
|
||||||
self.cross_couple_upper_ypos = self.inverter_nmos_ypos \
|
self.cross_couple_upper_ypos = self.inverter_nmos_ypos \
|
||||||
+ self.inverter_nmos.active_height \
|
+ self.inverter_nmos.active_height \
|
||||||
+ max(self.poly_to_active,
|
+ max(self.poly_to_active,
|
||||||
self.m1_space + inverter_nmos_contact_extension) \
|
self.m1_space + inverter_nmos_contact_extension) \
|
||||||
+ self.poly_to_polycontact \
|
+ self.poly_to_contact \
|
||||||
+ 1.5 * contact.poly.width
|
+ 1.5 * contact.poly_contact.width
|
||||||
|
|
||||||
# spacing between wordlines (and gnd)
|
# spacing between wordlines (and gnd)
|
||||||
self.m1_offset = -0.5 * self.m1_width
|
self.m1_offset = -0.5 * self.m1_width
|
||||||
|
|
@ -263,9 +262,9 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
# spacing for vdd
|
# spacing for vdd
|
||||||
implant_constraint = max(inverter_pmos_contact_extension, 0) \
|
implant_constraint = max(inverter_pmos_contact_extension, 0) \
|
||||||
+ 2 * self.implant_enclose_active \
|
+ 2 * self.implant_enclose_active \
|
||||||
+ 0.5 * (contact.well.width - self.m1_width)
|
+ 0.5*(self.inverter_pmos.active_contact.height - self.m1_width)
|
||||||
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
|
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
|
||||||
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
|
self.vdd_offset = max(implant_constraint, metal1_constraint) + self.m1_width
|
||||||
|
|
||||||
# read port dimensions
|
# read port dimensions
|
||||||
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
|
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
|
||||||
|
|
@ -276,7 +275,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
Calculate positions that describe the edges
|
Calculate positions that describe the edges
|
||||||
and dimensions of the cell
|
and dimensions of the cell
|
||||||
"""
|
"""
|
||||||
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch
|
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
|
||||||
self.topmost_ypos = self.inverter_nmos_ypos \
|
self.topmost_ypos = self.inverter_nmos_ypos \
|
||||||
+ self.inverter_nmos.active_height \
|
+ self.inverter_nmos.active_height \
|
||||||
+ self.inverter_gap \
|
+ self.inverter_gap \
|
||||||
|
|
@ -291,7 +290,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
(self.write_nmos.active_width + self.write_port_spacing) \
|
(self.write_nmos.active_width + self.write_port_spacing) \
|
||||||
- self.num_r_ports * \
|
- self.num_r_ports * \
|
||||||
(self.read_port_width + self.read_port_spacing) \
|
(self.read_port_width + self.read_port_spacing) \
|
||||||
- self.bitline_offset - 0.5 * contact.m1m2.width
|
- self.bitline_offset - 0.5 * contact.m1_via.width
|
||||||
|
|
||||||
self.width = -2 * self.leftmost_xpos
|
self.width = -2 * self.leftmost_xpos
|
||||||
self.height = self.topmost_ypos - self.botmost_ypos
|
self.height = self.topmost_ypos - self.botmost_ypos
|
||||||
|
|
@ -369,29 +368,28 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
self.inverter_pmos_right.get_pin("G").bc()])
|
self.inverter_pmos_right.get_pin("G").bc()])
|
||||||
|
|
||||||
# connect output (drain/source) of inverters
|
# connect output (drain/source) of inverters
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.inverter_nmos_left.get_pin("D").uc(),
|
[self.inverter_nmos_left.get_pin("D").uc(),
|
||||||
self.inverter_pmos_left.get_pin("D").bc()],
|
self.inverter_pmos_left.get_pin("D").bc()],
|
||||||
width=contact.active.second_layer_width)
|
width=self.inverter_nmos_left.get_pin("D").width())
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.inverter_nmos_right.get_pin("S").uc(),
|
[self.inverter_nmos_right.get_pin("S").uc(),
|
||||||
self.inverter_pmos_right.get_pin("S").bc()],
|
self.inverter_pmos_right.get_pin("S").bc()],
|
||||||
width=contact.active.second_layer_width)
|
width=self.inverter_nmos_right.get_pin("S").width())
|
||||||
|
|
||||||
# add contacts to connect gate poly to drain/source
|
# add contacts to connect gate poly to drain/source
|
||||||
# metal1 (to connect Q to Q_bar)
|
# metal1 (to connect Q to Q_bar)
|
||||||
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \
|
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \
|
||||||
+ 0.5 * contact.poly.height,
|
+ 0.5 * contact.poly_contact.height,
|
||||||
self.cross_couple_upper_ypos)
|
self.cross_couple_upper_ypos)
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=contact_offset_left,
|
offset=contact_offset_left,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
|
|
||||||
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \
|
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \
|
||||||
- 0.5*contact.poly.height,
|
- 0.5*contact.poly_contact.height,
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=contact_offset_right,
|
offset=contact_offset_right,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
|
|
@ -417,12 +415,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
def route_rails(self):
|
def route_rails(self):
|
||||||
""" Adds gnd and vdd rails and connects them to the inverters """
|
""" Adds gnd and vdd rails and connects them to the inverters """
|
||||||
# Add rails for vdd and gnd
|
# Add rails for vdd and gnd
|
||||||
gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch
|
gnd_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
|
||||||
self.gnd_position = vector(0, gnd_ypos)
|
self.gnd_position = vector(0, gnd_ypos)
|
||||||
self.add_rect_center(layer="metal1",
|
self.add_rect_center(layer="m1",
|
||||||
offset=self.gnd_position,
|
offset=self.gnd_position,
|
||||||
width=self.width)
|
width=self.width)
|
||||||
self.add_power_pin("gnd", vector(0, gnd_ypos))
|
self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H"))
|
||||||
|
|
||||||
|
|
||||||
vdd_ypos = self.inverter_nmos_ypos \
|
vdd_ypos = self.inverter_nmos_ypos \
|
||||||
|
|
@ -431,10 +429,10 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
+ self.inverter_pmos.active_height \
|
+ self.inverter_pmos.active_height \
|
||||||
+ self.vdd_offset
|
+ self.vdd_offset
|
||||||
self.vdd_position = vector(0, vdd_ypos)
|
self.vdd_position = vector(0, vdd_ypos)
|
||||||
self.add_rect_center(layer="metal1",
|
self.add_rect_center(layer="m1",
|
||||||
offset=self.vdd_position,
|
offset=self.vdd_position,
|
||||||
width=self.width)
|
width=self.width)
|
||||||
self.add_power_pin("vdd", vector(0, vdd_ypos))
|
self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H"))
|
||||||
|
|
||||||
def create_readwrite_ports(self):
|
def create_readwrite_ports(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -501,10 +499,10 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
self.port_ypos])
|
self.port_ypos])
|
||||||
|
|
||||||
# add pin for RWWL
|
# add pin for RWWL
|
||||||
rwwl_ypos = self.m1_offset - k * self.m1_pitch
|
rwwl_ypos = self.m1_offset - k * self.m1_nonpref_pitch
|
||||||
self.rwwl_positions[k] = vector(0, rwwl_ypos)
|
self.rwwl_positions[k] = vector(0, rwwl_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
|
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=self.rwwl_positions[k],
|
offset=self.rwwl_positions[k],
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
|
|
@ -514,7 +512,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
+ 0.5 * self.m2_width
|
+ 0.5 * self.m2_width
|
||||||
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
|
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
|
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.rwbl_positions[k],
|
offset=self.rwbl_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -524,7 +522,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
- 0.5 * self.m2_width
|
- 0.5 * self.m2_width
|
||||||
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
|
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
|
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.rwbr_positions[k],
|
offset=self.rwbr_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -597,11 +595,11 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# add pin for WWL
|
# add pin for WWL
|
||||||
wwl_ypos = rwwl_ypos = self.m1_offset \
|
wwl_ypos = rwwl_ypos = self.m1_offset \
|
||||||
- self.num_rw_ports * self.m1_pitch \
|
- self.num_rw_ports * self.m1_nonpref_pitch \
|
||||||
- k * self.m1_pitch
|
- k * self.m1_nonpref_pitch
|
||||||
self.wwl_positions[k] = vector(0, wwl_ypos)
|
self.wwl_positions[k] = vector(0, wwl_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
|
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=self.wwl_positions[k],
|
offset=self.wwl_positions[k],
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
|
|
@ -611,7 +609,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
+ 0.5 * self.m2_width
|
+ 0.5 * self.m2_width
|
||||||
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
|
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
|
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.wbl_positions[k],
|
offset=self.wbl_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -621,7 +619,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
- 0.5 * self.m2_width
|
- 0.5 * self.m2_width
|
||||||
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
|
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.w_br_names[k],
|
self.add_layout_pin_rect_center(text=self.w_br_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.wbr_positions[k],
|
offset=self.wbr_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -723,12 +721,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# add pin for RWL
|
# add pin for RWL
|
||||||
rwl_ypos = rwwl_ypos = self.m1_offset \
|
rwl_ypos = rwwl_ypos = self.m1_offset \
|
||||||
- self.num_rw_ports * self.m1_pitch \
|
- self.num_rw_ports * self.m1_nonpref_pitch \
|
||||||
- self.num_w_ports * self.m1_pitch \
|
- self.num_w_ports * self.m1_nonpref_pitch \
|
||||||
- k * self.m1_pitch
|
- k * self.m1_nonpref_pitch
|
||||||
self.rwl_positions[k] = vector(0, rwl_ypos)
|
self.rwl_positions[k] = vector(0, rwl_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
|
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=self.rwl_positions[k],
|
offset=self.rwl_positions[k],
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
|
|
@ -738,7 +736,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
+ 0.5 * self.m2_width
|
+ 0.5 * self.m2_width
|
||||||
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
|
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
|
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.rbl_positions[k],
|
offset=self.rbl_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -748,7 +746,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
- 0.5 * self.m2_width
|
- 0.5 * self.m2_width
|
||||||
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
|
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
|
||||||
self.add_layout_pin_rect_center(text=self.r_br_names[k],
|
self.add_layout_pin_rect_center(text=self.r_br_names[k],
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=self.rbr_positions[k],
|
offset=self.rbr_positions[k],
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
|
@ -786,25 +784,25 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
# first transistor on either side of the cross coupled inverters
|
# first transistor on either side of the cross coupled inverters
|
||||||
# does not need to route to wordline on metal2
|
# does not need to route to wordline on metal2
|
||||||
if (k == 0) or (k == 1):
|
if (k == 0) or (k == 1):
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=port_contact_offset)
|
offset=port_contact_offset)
|
||||||
|
|
||||||
|
|
||||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[port_contact_offset, wl_contact_offset])
|
[port_contact_offset, wl_contact_offset])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=port_contact_offset)
|
offset=port_contact_offset)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=port_contact_offset)
|
offset=port_contact_offset)
|
||||||
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=wl_contact_offset,
|
offset=wl_contact_offset)
|
||||||
directions=("H", "H"))
|
|
||||||
|
|
||||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||||
self.add_path("metal2",
|
self.add_path("m2",
|
||||||
[port_contact_offset, wl_contact_offset])
|
[port_contact_offset, wl_contact_offset])
|
||||||
|
|
||||||
def route_bitlines(self):
|
def route_bitlines(self):
|
||||||
|
|
@ -839,11 +837,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# Leave bitline disconnected if a dummy cell
|
# Leave bitline disconnected if a dummy cell
|
||||||
if not self.dummy_bitcell:
|
if not self.dummy_bitcell:
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=port_contact_offest)
|
offset=port_contact_offest,
|
||||||
|
directions="nonpref")
|
||||||
|
|
||||||
self.add_path("metal2",
|
self.add_path("m2",
|
||||||
[port_contact_offest, bl_offset], width=contact.m1m2.height)
|
[port_contact_offest, bl_offset], width=contact.m1_via.height)
|
||||||
|
|
||||||
for k in range(self.total_ports):
|
for k in range(self.total_ports):
|
||||||
port_contact_offest = right_port_transistors[k].get_pin("D").center()
|
port_contact_offest = right_port_transistors[k].get_pin("D").center()
|
||||||
|
|
@ -851,11 +850,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# Leave bitline disconnected if a dummy cell
|
# Leave bitline disconnected if a dummy cell
|
||||||
if not self.dummy_bitcell:
|
if not self.dummy_bitcell:
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=port_contact_offest)
|
offset=port_contact_offest,
|
||||||
|
directions="nonpref")
|
||||||
|
|
||||||
self.add_path("metal2",
|
self.add_path("m2",
|
||||||
[port_contact_offest, br_offset], width=contact.m1m2.height)
|
[port_contact_offest, br_offset], width=contact.m1_via.height)
|
||||||
|
|
||||||
def route_supply(self):
|
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. Route inverter pmos to vdd. """
|
||||||
|
|
@ -868,30 +868,32 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
|
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
|
||||||
|
|
||||||
for position in nmos_contact_positions:
|
for position in nmos_contact_positions:
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=position)
|
offset=position,
|
||||||
|
directions=("V", "V"))
|
||||||
|
|
||||||
|
|
||||||
if position.x > 0:
|
if position.x > 0:
|
||||||
contact_correct = 0.5 * contact.m1m2.height
|
contact_correct = 0.5 * contact.m1_via.height
|
||||||
else:
|
else:
|
||||||
contact_correct = -0.5 * contact.m1m2.height
|
contact_correct = -0.5 * contact.m1_via.height
|
||||||
supply_offset = vector(position.x + contact_correct,
|
supply_offset = vector(position.x + contact_correct,
|
||||||
self.gnd_position.y)
|
self.gnd_position.y)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=supply_offset,
|
offset=supply_offset,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
self.add_path("metal2", [position, supply_offset])
|
self.add_path("m2", [position, supply_offset])
|
||||||
|
|
||||||
# route inverter pmos to vdd
|
# route inverter pmos to vdd
|
||||||
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x,
|
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x,
|
||||||
self.vdd_position.y)
|
self.vdd_position.y)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
[self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
||||||
|
|
||||||
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x,
|
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x,
|
||||||
self.vdd_position.y)
|
self.vdd_position.y)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
[self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
||||||
|
|
||||||
def route_readwrite_access(self):
|
def route_readwrite_access(self):
|
||||||
|
|
@ -904,14 +906,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
[self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
||||||
|
|
||||||
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x,
|
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x,
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
[self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
||||||
|
|
||||||
def route_write_access(self):
|
def route_write_access(self):
|
||||||
|
|
@ -924,14 +926,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
[self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
||||||
|
|
||||||
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x,
|
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x,
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
||||||
self.cross_couple_lower_ypos)
|
self.cross_couple_lower_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
[self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
||||||
|
|
||||||
def route_read_access(self):
|
def route_read_access(self):
|
||||||
|
|
@ -941,16 +943,17 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
"""
|
"""
|
||||||
# add poly to metal1 contacts for gates of the inverters
|
# add poly to metal1 contacts for gates of the inverters
|
||||||
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \
|
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \
|
||||||
- self.poly_to_polycontact - 0.5*contact.poly.width,
|
- self.poly_to_contact - 0.5*contact.poly_contact.width,
|
||||||
self.cross_couple_upper_ypos)
|
self.cross_couple_upper_ypos)
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=left_storage_contact,
|
offset=left_storage_contact,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
|
|
||||||
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \
|
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \
|
||||||
+ self.poly_to_polycontact + 0.5*contact.poly.width,
|
+ self.poly_to_contact + 0.5*contact.poly_contact.width,
|
||||||
self.cross_couple_upper_ypos)
|
self.cross_couple_upper_ypos)
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=right_storage_contact,
|
offset=right_storage_contact,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
|
|
@ -967,7 +970,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
+ vector(0,
|
+ vector(0,
|
||||||
self.gate_contact_yoffset - self.poly_extend_active)
|
self.gate_contact_yoffset - self.poly_extend_active)
|
||||||
|
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=port_contact_offset)
|
offset=port_contact_offset)
|
||||||
|
|
||||||
self.add_path("poly",
|
self.add_path("poly",
|
||||||
|
|
@ -975,14 +978,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x,
|
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x,
|
||||||
self.cross_couple_upper_ypos)
|
self.cross_couple_upper_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[port_contact_offset, mid, left_storage_contact])
|
[port_contact_offset, mid, left_storage_contact])
|
||||||
|
|
||||||
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \
|
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \
|
||||||
+ vector(0,
|
+ vector(0,
|
||||||
self.gate_contact_yoffset - self.poly_extend_active)
|
self.gate_contact_yoffset - self.poly_extend_active)
|
||||||
|
|
||||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
self.add_via_center(layers=self.poly_stack,
|
||||||
offset=port_contact_offset)
|
offset=port_contact_offset)
|
||||||
|
|
||||||
self.add_path("poly",
|
self.add_path("poly",
|
||||||
|
|
@ -990,51 +993,55 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x,
|
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x,
|
||||||
self.cross_couple_upper_ypos)
|
self.cross_couple_upper_ypos)
|
||||||
self.add_path("metal1",
|
self.add_path("m1",
|
||||||
[port_contact_offset, mid, right_storage_contact])
|
[port_contact_offset, mid, right_storage_contact])
|
||||||
|
|
||||||
def extend_well(self):
|
def extend_well(self):
|
||||||
"""
|
"""
|
||||||
Connects wells between ptx modules and places well contacts
|
Connects wells between ptx modules and places well contacts
|
||||||
"""
|
"""
|
||||||
# extend pwell to encompass entire nmos region of the cell up to the
|
if "pwell" in layer:
|
||||||
# height of the tallest nmos transistor
|
# extend pwell to encompass entire nmos region of the cell up to the
|
||||||
max_nmos_well_height = max(self.inverter_nmos.cell_well_height,
|
# height of the tallest nmos transistor
|
||||||
self.readwrite_nmos.cell_well_height,
|
max_nmos_well_height = max(self.inverter_nmos.well_height,
|
||||||
self.write_nmos.cell_well_height,
|
self.readwrite_nmos.well_height,
|
||||||
self.read_nmos.cell_well_height)
|
self.write_nmos.well_height,
|
||||||
well_height = max_nmos_well_height + self.port_ypos \
|
self.read_nmos.well_height)
|
||||||
- self.well_enclose_active - self.gnd_position.y
|
well_height = max_nmos_well_height + self.port_ypos \
|
||||||
offset = vector(self.leftmost_xpos, self.botmost_ypos)
|
- self.nwell_enclose_active - self.gnd_position.y
|
||||||
self.add_rect(layer="pwell",
|
# FIXME fudge factor xpos
|
||||||
offset=offset,
|
well_width = self.width + 2*self.nwell_enclose_active
|
||||||
width=self.width,
|
offset = vector(self.leftmost_xpos - self.nwell_enclose_active, self.botmost_ypos)
|
||||||
height=well_height)
|
self.add_rect(layer="pwell",
|
||||||
|
offset=offset,
|
||||||
|
width=well_width,
|
||||||
|
height=well_height)
|
||||||
|
|
||||||
# extend nwell to encompass inverter_pmos
|
# extend nwell to encompass inverter_pmos
|
||||||
# calculate offset of the left pmos well
|
# calculate offset of the left pmos well
|
||||||
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
if "nwell" in layer:
|
||||||
- self.well_enclose_active
|
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||||
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
- self.nwell_enclose_active
|
||||||
+ self.inverter_gap - self.well_enclose_active
|
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||||
|
+ self.inverter_gap - self.nwell_enclose_active
|
||||||
|
|
||||||
|
# calculate width of the two combined nwells
|
||||||
|
# calculate height to encompass nimplant connected to vdd
|
||||||
|
well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||||
|
+ 2 * self.nwell_enclose_active
|
||||||
|
well_height = self.vdd_position.y - inverter_well_ypos \
|
||||||
|
+ self.nwell_enclose_active + drc["minwidth_tx"]
|
||||||
|
|
||||||
# calculate width of the two combined nwells
|
offset = [inverter_well_xpos, inverter_well_ypos]
|
||||||
# calculate height to encompass nimplant connected to vdd
|
self.add_rect(layer="nwell",
|
||||||
well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
offset=offset,
|
||||||
+ 2 * self.well_enclose_active
|
width=well_width,
|
||||||
well_height = self.vdd_position.y - inverter_well_ypos \
|
height=well_height)
|
||||||
+ self.well_enclose_active + drc["minwidth_tx"]
|
|
||||||
|
|
||||||
offset = [inverter_well_xpos, inverter_well_ypos]
|
|
||||||
self.add_rect(layer="nwell",
|
|
||||||
offset=offset,
|
|
||||||
width=well_width,
|
|
||||||
height=well_height)
|
|
||||||
|
|
||||||
# add well contacts
|
# add well contacts
|
||||||
# connect pimplants to gnd
|
# connect pimplants to gnd
|
||||||
offset = vector(0, self.gnd_position.y)
|
offset = vector(0, self.gnd_position.y)
|
||||||
self.add_via_center(layers=("active", "contact", "metal1"),
|
self.add_via_center(layers=self.active_stack,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
directions=("H", "H"),
|
directions=("H", "H"),
|
||||||
implant_type="p",
|
implant_type="p",
|
||||||
|
|
@ -1042,7 +1049,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
|
|
||||||
# connect nimplants to vdd
|
# connect nimplants to vdd
|
||||||
offset = vector(0, self.vdd_position.y)
|
offset = vector(0, self.vdd_position.y)
|
||||||
self.add_via_center(layers=("active", "contact", "metal1"),
|
self.add_via_center(layers=self.active_stack,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
directions=("H", "H"),
|
directions=("H", "H"),
|
||||||
implant_type="n",
|
implant_type="n",
|
||||||
|
|
@ -1091,7 +1098,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
||||||
"""
|
"""
|
||||||
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
|
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
|
||||||
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
||||||
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|
self.add_path("m1", [Q_bar_pos, vdd_pos])
|
||||||
|
|
||||||
def get_storage_net_names(self):
|
def get_storage_net_names(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS,layer,drc,parameter
|
from tech import GDS,layer,drc,parameter,cell_properties
|
||||||
|
from tech import cell_properties as props
|
||||||
|
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
class replica_bitcell(design.design):
|
class replica_bitcell(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -17,10 +20,24 @@ class replica_bitcell(design.design):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
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"]
|
if cell_properties.bitcell.split_wl:
|
||||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"]
|
||||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT" , "POWER", "GROUND"]
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
else:
|
||||||
|
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||||
|
props.bitcell.cell_6t.pin.br,
|
||||||
|
props.bitcell.cell_6t.pin.wl,
|
||||||
|
props.bitcell.cell_6t.pin.vdd,
|
||||||
|
props.bitcell.cell_6t.pin.gnd]
|
||||||
|
|
||||||
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||||
|
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||||
|
else:
|
||||||
|
(width,height) = (0,0)
|
||||||
|
pin_map = []
|
||||||
|
|
||||||
def __init__(self, name=""):
|
def __init__(self, name=""):
|
||||||
# Ignore the name argument
|
# Ignore the name argument
|
||||||
|
|
@ -56,4 +73,4 @@ class replica_bitcell(design.design):
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import design
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS,layer,drc,parameter
|
from tech import GDS,layer,drc,parameter
|
||||||
|
from tech import cell_properties as props
|
||||||
|
|
||||||
class replica_bitcell_1rw_1r(design.design):
|
class replica_bitcell_1rw_1r(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -17,7 +18,15 @@ class replica_bitcell_1rw_1r(design.design):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
the technology library. """
|
the technology library. """
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.br1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1rw1r.pin.gnd]
|
||||||
|
|
||||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
|
(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"])
|
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
|
||||||
|
|
@ -47,14 +56,15 @@ class replica_bitcell_1rw_1r(design.design):
|
||||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||||
return 2*access_tx_cin
|
return 2*access_tx_cin
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||||
to use the add_graph_edges function."""
|
to use the add_graph_edges function."""
|
||||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||||
|
pins = props.bitcell.cell_1rw1r.pin
|
||||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||||
# Port 0 edges
|
# Port 0 edges
|
||||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
|
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self)
|
||||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
|
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self)
|
||||||
# Port 1 edges
|
# Port 1 edges
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import design
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS,layer,drc,parameter
|
from tech import GDS,layer,drc,parameter
|
||||||
|
from tech import cell_properties as props
|
||||||
|
|
||||||
class replica_bitcell_1w_1r(design.design):
|
class replica_bitcell_1w_1r(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -17,7 +18,15 @@ class replica_bitcell_1w_1r(design.design):
|
||||||
is a hand-made cell, so the layout and netlist should be available in
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
the technology library. """
|
the technology library. """
|
||||||
|
|
||||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.br0,
|
||||||
|
props.bitcell.cell_1w1r.pin.bl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.br1,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1w1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1w1r.pin.vdd,
|
||||||
|
props.bitcell.cell_1w1r.pin.gnd]
|
||||||
|
|
||||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"])
|
(width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"])
|
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"])
|
||||||
|
|
@ -47,13 +56,14 @@ class replica_bitcell_1w_1r(design.design):
|
||||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||||
return 2*access_tx_cin
|
return 2*access_tx_cin
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||||
to use the add_graph_edges function."""
|
to use the add_graph_edges function."""
|
||||||
debug.info(1,'Adding edges for {}'.format(inst_name))
|
debug.info(1,'Adding edges for {}'.format(inst_name))
|
||||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||||
|
pins = props.bitcell.cell_1w1r.pin
|
||||||
#Edges hardcoded here. Essentially wl->bl/br for the read port.
|
#Edges hardcoded here. Essentially wl->bl/br for the read port.
|
||||||
# Port 1 edges
|
# Port 1 edges
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||||
# Port 0 is a write port, so its timing is not considered here.
|
# Port 0 is a write port, so its timing is not considered here.
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class replica_pbitcell(design.design):
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
self.add_boundary()
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
|
|
@ -84,4 +85,4 @@ class replica_pbitcell(design.design):
|
||||||
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
|
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
|
||||||
self.copy_layout_pin(self.prbc_inst, "vdd")
|
self.copy_layout_pin(self.prbc_inst, "vdd")
|
||||||
self.copy_layout_pin(self.prbc_inst, "gnd")
|
self.copy_layout_pin(self.prbc_inst, "gnd")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
import utils
|
||||||
|
from tech import GDS, layer
|
||||||
|
from tech import cell_properties as props
|
||||||
|
import bitcell_base
|
||||||
|
|
||||||
|
|
||||||
|
class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base):
|
||||||
|
"""
|
||||||
|
A single bit cell which is forced to store a 0.
|
||||||
|
This module implements the single memory cell used in the design. It
|
||||||
|
is a hand-made cell, so the layout and netlist should be available in
|
||||||
|
the technology library. """
|
||||||
|
|
||||||
|
pin_names = [props.bitcell.cell_1rw1r.pin.wl0,
|
||||||
|
props.bitcell.cell_1rw1r.pin.wl1,
|
||||||
|
props.bitcell.cell_1rw1r.pin.gnd]
|
||||||
|
|
||||||
|
type_list = ["INPUT", "INPUT", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names,
|
||||||
|
"row_cap_cell_1rw_1r",
|
||||||
|
GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name=""):
|
||||||
|
# Ignore the name argument
|
||||||
|
bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r")
|
||||||
|
debug.info(2, "Create row_cap bitcell 1rw+1r object")
|
||||||
|
|
||||||
|
self.width = row_cap_bitcell_1rw_1r.width
|
||||||
|
self.height = row_cap_bitcell_1rw_1r.height
|
||||||
|
self.pin_map = row_cap_bitcell_1rw_1r.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
self.no_instances = True
|
||||||
|
|
@ -59,7 +59,8 @@ class delay(simulation):
|
||||||
""" Create measurement names. The names themselves currently define the type of measurement """
|
""" Create measurement names. The names themselves currently define the type of measurement """
|
||||||
|
|
||||||
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
|
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
|
||||||
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
|
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power",
|
||||||
|
"disabled_read0_power", "disabled_read1_power", "disabled_write0_power", "disabled_write1_power"]
|
||||||
# self.voltage_when_names = ["volt_bl", "volt_br"]
|
# self.voltage_when_names = ["volt_bl", "volt_br"]
|
||||||
# self.bitline_delay_names = ["delay_bl", "delay_br"]
|
# self.bitline_delay_names = ["delay_bl", "delay_br"]
|
||||||
|
|
||||||
|
|
@ -108,6 +109,11 @@ class delay(simulation):
|
||||||
self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3))
|
self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3))
|
||||||
self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO
|
self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO
|
||||||
|
|
||||||
|
self.read_lib_meas.append(power_measure("disabled_read1_power", "RISE", measure_scale=1e3))
|
||||||
|
self.read_lib_meas[-1].meta_str = "disabled_read1"
|
||||||
|
self.read_lib_meas.append(power_measure("disabled_read0_power", "FALL", measure_scale=1e3))
|
||||||
|
self.read_lib_meas[-1].meta_str = "disabled_read0"
|
||||||
|
|
||||||
# This will later add a half-period to the spice time delay. Only for reading 0.
|
# This will later add a half-period to the spice time delay. Only for reading 0.
|
||||||
for obj in self.read_lib_meas:
|
for obj in self.read_lib_meas:
|
||||||
if obj.meta_str is sram_op.READ_ZERO:
|
if obj.meta_str is sram_op.READ_ZERO:
|
||||||
|
|
@ -155,6 +161,11 @@ class delay(simulation):
|
||||||
self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3))
|
self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3))
|
||||||
self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO
|
self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO
|
||||||
|
|
||||||
|
self.write_lib_meas.append(power_measure("disabled_write1_power", "RISE", measure_scale=1e3))
|
||||||
|
self.write_lib_meas[-1].meta_str = "disabled_write1"
|
||||||
|
self.write_lib_meas.append(power_measure("disabled_write0_power", "FALL", measure_scale=1e3))
|
||||||
|
self.write_lib_meas[-1].meta_str = "disabled_write0"
|
||||||
|
|
||||||
write_measures = []
|
write_measures = []
|
||||||
write_measures.append(self.write_lib_meas)
|
write_measures.append(self.write_lib_meas)
|
||||||
write_measures.append(self.create_write_bit_measures())
|
write_measures.append(self.create_write_bit_measures())
|
||||||
|
|
@ -170,39 +181,40 @@ class delay(simulation):
|
||||||
meas.targ_name_no_port))
|
meas.targ_name_no_port))
|
||||||
self.dout_volt_meas[-1].meta_str = meas.meta_str
|
self.dout_volt_meas[-1].meta_str = meas.meta_str
|
||||||
|
|
||||||
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9)
|
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name+"{}", "FALL", "RISE", measure_scale=1e9)
|
||||||
self.sen_meas.meta_str = sram_op.READ_ZERO
|
self.sen_meas.meta_str = sram_op.READ_ZERO
|
||||||
self.sen_meas.meta_add_delay = True
|
self.sen_meas.meta_add_delay = True
|
||||||
|
self.dout_volt_meas.append(self.sen_meas)
|
||||||
|
|
||||||
return self.dout_volt_meas+[self.sen_meas]
|
return self.dout_volt_meas
|
||||||
|
|
||||||
def create_read_bit_measures(self):
|
def create_read_bit_measures(self):
|
||||||
""" Adds bit measurements for read0 and read1 cycles """
|
""" Adds bit measurements for read0 and read1 cycles """
|
||||||
|
|
||||||
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
self.read_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||||
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
|
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
|
||||||
for cycle in meas_cycles:
|
for cycle in meas_cycles:
|
||||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||||
for polarity,meas in single_bit_meas.items():
|
for polarity,meas in single_bit_meas.items():
|
||||||
meas.meta_str = cycle
|
meas.meta_str = cycle
|
||||||
self.bit_meas[polarity].append(meas)
|
self.read_bit_meas[polarity].append(meas)
|
||||||
# Dictionary values are lists, reduce to a single list of measurements
|
# Dictionary values are lists, reduce to a single list of measurements
|
||||||
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
|
return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list]
|
||||||
|
|
||||||
def create_write_bit_measures(self):
|
def create_write_bit_measures(self):
|
||||||
""" Adds bit measurements for write0 and write1 cycles """
|
""" Adds bit measurements for write0 and write1 cycles """
|
||||||
|
|
||||||
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
self.write_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||||
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
|
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
|
||||||
for cycle in meas_cycles:
|
for cycle in meas_cycles:
|
||||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||||
for polarity,meas in single_bit_meas.items():
|
for polarity,meas in single_bit_meas.items():
|
||||||
meas.meta_str = cycle
|
meas.meta_str = cycle
|
||||||
self.bit_meas[polarity].append(meas)
|
self.write_bit_meas[polarity].append(meas)
|
||||||
# Dictionary values are lists, reduce to a single list of measurements
|
# Dictionary values are lists, reduce to a single list of measurements
|
||||||
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
|
return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list]
|
||||||
|
|
||||||
def get_bit_measures(self, meas_tag, probe_address, probe_data):
|
def get_bit_measures(self, meas_tag, probe_address, probe_data):
|
||||||
"""
|
"""
|
||||||
|
|
@ -225,9 +237,10 @@ class delay(simulation):
|
||||||
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
|
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
|
||||||
|
|
||||||
# Bit measures, measurements times to be defined later. The measurement names must be unique
|
# Bit measures, measurements times to be defined later. The measurement names must be unique
|
||||||
# but they is enforced externally
|
# but they is enforced externally. {} added to names to differentiate between ports allow the
|
||||||
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False)
|
# measurements are independent of the ports
|
||||||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False)
|
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name)
|
||||||
|
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name)
|
||||||
|
|
||||||
return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas}
|
return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas}
|
||||||
|
|
||||||
|
|
@ -267,10 +280,33 @@ class delay(simulation):
|
||||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||||
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
||||||
|
|
||||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
sen_with_port = self.get_sen_name(self.graph.all_paths)
|
||||||
|
if sen_with_port.endswith(str(port)):
|
||||||
|
self.sen_name = sen_with_port[:-len(str(port))]
|
||||||
|
else:
|
||||||
|
self.sen_name = sen_with_port
|
||||||
|
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
|
||||||
|
|
||||||
debug.info(2,"s_en name = {}".format(self.sen_name))
|
debug.info(2,"s_en name = {}".format(self.sen_name))
|
||||||
|
|
||||||
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
||||||
|
port_pos = -1-len(str(self.probe_data))-len(str(port))
|
||||||
|
|
||||||
|
if bl_name_port.endswith(str(port)+"_"+str(self.probe_data)):
|
||||||
|
self.bl_name = bl_name_port[:port_pos] +"{}"+ bl_name_port[port_pos+len(str(port)):]
|
||||||
|
elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||||
|
self.bl_name = bl_name_port
|
||||||
|
else:
|
||||||
|
self.bl_name = bl_name_port
|
||||||
|
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
||||||
|
|
||||||
|
if br_name_port.endswith(str(port)+"_"+str(self.probe_data)):
|
||||||
|
self.br_name = br_name_port[:port_pos] +"{}"+ br_name_port[port_pos+len(str(port)):]
|
||||||
|
elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||||
|
self.br_name = br_name_port
|
||||||
|
else:
|
||||||
|
self.br_name = br_name_port
|
||||||
|
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
||||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||||
else:
|
else:
|
||||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||||
|
|
@ -283,8 +319,9 @@ class delay(simulation):
|
||||||
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1)
|
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1)
|
||||||
self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1)
|
self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1)
|
||||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||||
|
|
||||||
|
|
||||||
def get_sen_name(self, paths):
|
def get_sen_name(self, paths, assumed_port=None):
|
||||||
"""
|
"""
|
||||||
Gets the signal name associated with the sense amp enable from input paths.
|
Gets the signal name associated with the sense amp enable from input paths.
|
||||||
Only expects a single path to contain the sen signal name.
|
Only expects a single path to contain the sen signal name.
|
||||||
|
|
@ -674,8 +711,9 @@ class delay(simulation):
|
||||||
if (time_out <= 0):
|
if (time_out <= 0):
|
||||||
debug.error("Timed out, could not find a feasible period.",2)
|
debug.error("Timed out, could not find a feasible period.",2)
|
||||||
|
|
||||||
# Clear any write target ports and set read port
|
# Write ports are assumed non-critical to timing, so the first available is used
|
||||||
self.targ_write_ports = [port]
|
self.targ_write_ports = [self.write_ports[0]]
|
||||||
|
# Set target read port for simulation
|
||||||
self.targ_read_ports = [port]
|
self.targ_read_ports = [port]
|
||||||
|
|
||||||
debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port))
|
debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port))
|
||||||
|
|
@ -689,7 +727,7 @@ class delay(simulation):
|
||||||
if not success:
|
if not success:
|
||||||
feasible_period = 2 * feasible_period
|
feasible_period = 2 * feasible_period
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews
|
# Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews
|
||||||
feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname]
|
feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname]
|
||||||
feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname]
|
feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname]
|
||||||
|
|
@ -756,12 +794,10 @@ class delay(simulation):
|
||||||
# Loop through all targeted ports and collect delays and powers.
|
# Loop through all targeted ports and collect delays and powers.
|
||||||
result = [{} for i in self.all_ports]
|
result = [{} for i in self.all_ports]
|
||||||
|
|
||||||
|
|
||||||
# First, check that the memory has the right values at the right times
|
|
||||||
if not self.check_bit_measures():
|
|
||||||
return(False,{})
|
|
||||||
|
|
||||||
for port in self.targ_write_ports:
|
for port in self.targ_write_ports:
|
||||||
|
if not self.check_bit_measures(self.write_bit_meas, port):
|
||||||
|
return(False,{})
|
||||||
|
|
||||||
debug.info(2, "Checking write values for port {}".format(port))
|
debug.info(2, "Checking write values for port {}".format(port))
|
||||||
write_port_dict = {}
|
write_port_dict = {}
|
||||||
for measure in self.write_lib_meas:
|
for measure in self.write_lib_meas:
|
||||||
|
|
@ -773,6 +809,10 @@ class delay(simulation):
|
||||||
|
|
||||||
|
|
||||||
for port in self.targ_read_ports:
|
for port in self.targ_read_ports:
|
||||||
|
# First, check that the memory has the right values at the right times
|
||||||
|
if not self.check_bit_measures(self.read_bit_meas, port):
|
||||||
|
return(False,{})
|
||||||
|
|
||||||
debug.info(2, "Checking read delay values for port {}".format(port))
|
debug.info(2, "Checking read delay values for port {}".format(port))
|
||||||
# Check sen timing, then bitlines, then general measurements.
|
# Check sen timing, then bitlines, then general measurements.
|
||||||
if not self.check_sen_measure(port):
|
if not self.check_sen_measure(port):
|
||||||
|
|
@ -849,15 +889,15 @@ class delay(simulation):
|
||||||
return dout_success
|
return dout_success
|
||||||
|
|
||||||
|
|
||||||
def check_bit_measures(self):
|
def check_bit_measures(self, bit_measures, port):
|
||||||
"""
|
"""
|
||||||
Checks the measurements which represent the internal storage voltages
|
Checks the measurements which represent the internal storage voltages
|
||||||
at the end of the read cycle.
|
at the end of the read cycle.
|
||||||
"""
|
"""
|
||||||
success = False
|
success = False
|
||||||
for polarity, meas_list in self.bit_meas.items():
|
for polarity, meas_list in bit_measures.items():
|
||||||
for meas in meas_list:
|
for meas in meas_list:
|
||||||
val = meas.retrieve_measure()
|
val = meas.retrieve_measure(port=port)
|
||||||
debug.info(2,"{}={}".format(meas.name, val))
|
debug.info(2,"{}={}".format(meas.name, val))
|
||||||
if type(val) != float:
|
if type(val) != float:
|
||||||
continue
|
continue
|
||||||
|
|
@ -990,7 +1030,8 @@ class delay(simulation):
|
||||||
|
|
||||||
# Binary search algorithm to find the min period (max frequency) of input port
|
# Binary search algorithm to find the min period (max frequency) of input port
|
||||||
time_out = 25
|
time_out = 25
|
||||||
self.targ_write_ports = [port]
|
# Write ports are assumed non-critical to timing, so the first available is used
|
||||||
|
self.targ_write_ports = [self.write_ports[0]]
|
||||||
self.targ_read_ports = [port]
|
self.targ_read_ports = [port]
|
||||||
while True:
|
while True:
|
||||||
time_out -= 1
|
time_out -= 1
|
||||||
|
|
@ -1088,7 +1129,8 @@ class delay(simulation):
|
||||||
self.trimsp.set_configuration(self.num_banks,
|
self.trimsp.set_configuration(self.num_banks,
|
||||||
self.num_rows,
|
self.num_rows,
|
||||||
self.num_cols,
|
self.num_cols,
|
||||||
self.word_size)
|
self.word_size,
|
||||||
|
self.num_spare_rows)
|
||||||
self.trimsp.trim(self.probe_address,self.probe_data)
|
self.trimsp.trim(self.probe_address,self.probe_data)
|
||||||
else:
|
else:
|
||||||
# The non-reduced netlist file when it is disabled
|
# The non-reduced netlist file when it is disabled
|
||||||
|
|
@ -1220,6 +1262,9 @@ class delay(simulation):
|
||||||
write_port)
|
write_port)
|
||||||
self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1
|
self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1
|
||||||
|
|
||||||
|
self.add_noop_clock_one_port(write_port)
|
||||||
|
self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times)-1
|
||||||
|
|
||||||
# This also ensures we will have a H->L transition on the next read
|
# This also ensures we will have a H->L transition on the next read
|
||||||
self.add_read("R data 1 address {} to set dout caps".format(inverse_address),
|
self.add_read("R data 1 address {} to set dout caps".format(inverse_address),
|
||||||
inverse_address,
|
inverse_address,
|
||||||
|
|
@ -1230,6 +1275,10 @@ class delay(simulation):
|
||||||
read_port)
|
read_port)
|
||||||
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1
|
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1
|
||||||
|
|
||||||
|
self.add_noop_clock_one_port(read_port)
|
||||||
|
self.measure_cycles[read_port]["disabled_read0"] = len(self.cycle_times) - 1
|
||||||
|
|
||||||
|
|
||||||
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
|
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
|
||||||
|
|
||||||
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
||||||
|
|
@ -1239,12 +1288,19 @@ class delay(simulation):
|
||||||
write_port)
|
write_port)
|
||||||
self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1
|
self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1
|
||||||
|
|
||||||
|
self.add_noop_clock_one_port(write_port)
|
||||||
|
self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times)-1
|
||||||
|
|
||||||
self.add_write("W data 0 address {} to clear din caps".format(inverse_address),
|
self.add_write("W data 0 address {} to clear din caps".format(inverse_address),
|
||||||
inverse_address,
|
inverse_address,
|
||||||
data_zeros,
|
data_zeros,
|
||||||
wmask_ones,
|
wmask_ones,
|
||||||
write_port)
|
write_port)
|
||||||
|
|
||||||
|
self.add_noop_clock_one_port(read_port)
|
||||||
|
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
|
||||||
|
|
||||||
|
|
||||||
# This also ensures we will have a L->H transition on the next read
|
# This also ensures we will have a L->H transition on the next read
|
||||||
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address),
|
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address),
|
||||||
inverse_address,
|
inverse_address,
|
||||||
|
|
@ -1278,8 +1334,8 @@ class delay(simulation):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Using this requires setting at least one port to target for simulation.
|
# Using this requires setting at least one port to target for simulation.
|
||||||
if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0:
|
if len(self.targ_write_ports) == 0 or len(self.targ_read_ports) == 0:
|
||||||
debug.error("No port selected for characterization.",1)
|
debug.error("Write and read port must be specified for characterization.",1)
|
||||||
self.set_stimulus_variables()
|
self.set_stimulus_variables()
|
||||||
|
|
||||||
# Get any available read/write port in case only a single write or read ports is being characterized.
|
# Get any available read/write port in case only a single write or read ports is being characterized.
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,18 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import sys,re,shutil
|
|
||||||
import copy
|
|
||||||
import collections
|
import collections
|
||||||
from design import design
|
|
||||||
import debug
|
import debug
|
||||||
import math
|
|
||||||
import tech
|
|
||||||
import random
|
import random
|
||||||
from .stimuli import *
|
from .stimuli import *
|
||||||
from .charutils import *
|
from .charutils import *
|
||||||
import utils
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from .simulation import simulation
|
from .simulation import simulation
|
||||||
from .delay import delay
|
# from .delay import delay
|
||||||
import graph_util
|
import graph_util
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
||||||
|
|
||||||
class functional(simulation):
|
class functional(simulation):
|
||||||
"""
|
"""
|
||||||
Functions to write random data values to a random address then read them back and check
|
Functions to write random data values to a random address then read them back and check
|
||||||
|
|
@ -40,6 +35,9 @@ class functional(simulation):
|
||||||
else:
|
else:
|
||||||
self.num_wmasks = 0
|
self.num_wmasks = 0
|
||||||
|
|
||||||
|
if not self.num_spare_cols:
|
||||||
|
self.num_spare_cols = 0
|
||||||
|
|
||||||
self.set_corner(corner)
|
self.set_corner(corner)
|
||||||
self.set_spice_constants()
|
self.set_spice_constants()
|
||||||
self.set_stimulus_variables()
|
self.set_stimulus_variables()
|
||||||
|
|
@ -57,7 +55,6 @@ class functional(simulation):
|
||||||
self.read_check = []
|
self.read_check = []
|
||||||
self.read_results = []
|
self.read_results = []
|
||||||
|
|
||||||
|
|
||||||
def run(self, feasible_period=None):
|
def run(self, feasible_period=None):
|
||||||
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
||||||
self.period = feasible_period
|
self.period = feasible_period
|
||||||
|
|
@ -82,10 +79,11 @@ class functional(simulation):
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
checks = []
|
checks = []
|
||||||
if port in self.read_ports:
|
if port in self.read_ports:
|
||||||
checks.append((self.addr_value[port],"addr"))
|
checks.append((self.addr_value[port], "addr"))
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
checks.append((self.data_value[port],"data"))
|
checks.append((self.data_value[port], "data"))
|
||||||
checks.append((self.wmask_value[port],"wmask"))
|
checks.append((self.wmask_value[port], "wmask"))
|
||||||
|
checks.append((self.spare_wen_value[port], "spare_wen"))
|
||||||
|
|
||||||
for (val, name) in checks:
|
for (val, name) in checks:
|
||||||
debug.check(len(self.cycle_times)==len(val),
|
debug.check(len(self.cycle_times)==len(val),
|
||||||
|
|
@ -104,15 +102,15 @@ class functional(simulation):
|
||||||
r_ops = ["noop", "read"]
|
r_ops = ["noop", "read"]
|
||||||
|
|
||||||
# First cycle idle is always an idle cycle
|
# First cycle idle is always an idle cycle
|
||||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current)
|
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||||
self.add_noop_all_ports(comment)
|
self.add_noop_all_ports(comment)
|
||||||
|
|
||||||
# 1. Write all the write ports first to seed a bunch of locations.
|
# 1. Write all the write ports first to seed a bunch of locations.
|
||||||
for port in self.write_ports:
|
for port in self.write_ports:
|
||||||
addr = self.gen_addr()
|
addr = self.gen_addr()
|
||||||
word = self.gen_data()
|
word = self.gen_data()
|
||||||
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current)
|
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||||
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port)
|
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||||
self.stored_words[addr] = word
|
self.stored_words[addr] = word
|
||||||
|
|
||||||
# All other read-only ports are noops.
|
# All other read-only ports are noops.
|
||||||
|
|
@ -131,7 +129,7 @@ class functional(simulation):
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
self.add_noop_one_port(port)
|
self.add_noop_one_port(port)
|
||||||
else:
|
else:
|
||||||
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current)
|
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
||||||
self.add_read_one_port(comment, addr, port)
|
self.add_read_one_port(comment, addr, port)
|
||||||
self.add_read_check(word, port)
|
self.add_read_check(word, port)
|
||||||
self.cycle_times.append(self.t_current)
|
self.cycle_times.append(self.t_current)
|
||||||
|
|
@ -160,13 +158,13 @@ class functional(simulation):
|
||||||
self.add_noop_one_port(port)
|
self.add_noop_one_port(port)
|
||||||
else:
|
else:
|
||||||
word = self.gen_data()
|
word = self.gen_data()
|
||||||
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current)
|
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||||
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port)
|
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||||
self.stored_words[addr] = word
|
self.stored_words[addr] = word
|
||||||
w_addrs.append(addr)
|
w_addrs.append(addr)
|
||||||
elif op == "partial_write":
|
elif op == "partial_write":
|
||||||
# write only to a word that's been written to
|
# write only to a word that's been written to
|
||||||
(addr,old_word) = self.get_data()
|
(addr, old_word) = self.get_data()
|
||||||
# two ports cannot write to the same address
|
# two ports cannot write to the same address
|
||||||
if addr in w_addrs:
|
if addr in w_addrs:
|
||||||
self.add_noop_one_port(port)
|
self.add_noop_one_port(port)
|
||||||
|
|
@ -179,7 +177,7 @@ class functional(simulation):
|
||||||
self.stored_words[addr] = new_word
|
self.stored_words[addr] = new_word
|
||||||
w_addrs.append(addr)
|
w_addrs.append(addr)
|
||||||
else:
|
else:
|
||||||
(addr,word) = random.choice(list(self.stored_words.items()))
|
(addr, word) = random.choice(list(self.stored_words.items()))
|
||||||
# The write driver is not sized sufficiently to drive through the two
|
# The write driver is not sized sufficiently to drive through the two
|
||||||
# bitcell access transistors to the read port. So, for now, we do not allow
|
# bitcell access transistors to the read port. So, for now, we do not allow
|
||||||
# a simultaneous write and read to the same address on different ports. This
|
# a simultaneous write and read to the same address on different ports. This
|
||||||
|
|
@ -187,7 +185,7 @@ class functional(simulation):
|
||||||
if addr in w_addrs:
|
if addr in w_addrs:
|
||||||
self.add_noop_one_port(port)
|
self.add_noop_one_port(port)
|
||||||
else:
|
else:
|
||||||
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current)
|
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
||||||
self.add_read_one_port(comment, addr, port)
|
self.add_read_one_port(comment, addr, port)
|
||||||
self.add_read_check(word, port)
|
self.add_read_check(word, port)
|
||||||
|
|
||||||
|
|
@ -195,7 +193,7 @@ class functional(simulation):
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
|
|
||||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current)
|
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||||
self.add_noop_all_ports(comment)
|
self.add_noop_all_ports(comment)
|
||||||
|
|
||||||
def gen_masked_data(self, old_word, word, wmask):
|
def gen_masked_data(self, old_word, word, wmask):
|
||||||
|
|
@ -209,7 +207,7 @@ class functional(simulation):
|
||||||
if wmask[bit] == "0":
|
if wmask[bit] == "0":
|
||||||
lower = bit * self.write_size
|
lower = bit * self.write_size
|
||||||
upper = lower + self.write_size - 1
|
upper = lower + self.write_size - 1
|
||||||
new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:]
|
new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:]
|
||||||
|
|
||||||
return new_word
|
return new_word
|
||||||
|
|
||||||
|
|
@ -219,15 +217,15 @@ class functional(simulation):
|
||||||
self.check
|
self.check
|
||||||
except:
|
except:
|
||||||
self.check = 0
|
self.check = 0
|
||||||
self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check])
|
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
|
||||||
self.check += 1
|
self.check += 1
|
||||||
|
|
||||||
def read_stim_results(self):
|
def read_stim_results(self):
|
||||||
# Extract dout values from spice timing.lis
|
# Extract dout values from spice timing.lis
|
||||||
for (word, dout_port, eo_period, check) in self.read_check:
|
for (word, dout_port, eo_period, check) in self.read_check:
|
||||||
sp_read_value = ""
|
sp_read_value = ""
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check))
|
||||||
if value > self.v_high:
|
if value > self.v_high:
|
||||||
sp_read_value = "1" + sp_read_value
|
sp_read_value = "1" + sp_read_value
|
||||||
elif value < self.v_low:
|
elif value < self.v_low:
|
||||||
|
|
@ -278,17 +276,24 @@ class functional(simulation):
|
||||||
# wmask must be reversed since a python list goes right to left and sram bits go left to right.
|
# wmask must be reversed since a python list goes right to left and sram bits go left to right.
|
||||||
return wmask[::-1]
|
return wmask[::-1]
|
||||||
|
|
||||||
|
|
||||||
def gen_data(self):
|
def gen_data(self):
|
||||||
""" Generates a random word to write. """
|
""" Generates a random word to write. """
|
||||||
random_value = random.randint(0,(2**self.word_size)-1)
|
if not self.num_spare_cols:
|
||||||
data_bits = self.convert_to_bin(random_value,False)
|
random_value = random.randint(0, (2 ** self.word_size) - 1)
|
||||||
|
else:
|
||||||
|
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
|
||||||
|
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
|
||||||
|
random_value = random_value1 + random_value2
|
||||||
|
data_bits = self.convert_to_bin(random_value, False)
|
||||||
return data_bits
|
return data_bits
|
||||||
|
|
||||||
def gen_addr(self):
|
def gen_addr(self):
|
||||||
""" Generates a random address value to write to. """
|
""" Generates a random address value to write to. """
|
||||||
random_value = random.randint(0,(2**self.addr_size)-1)
|
if self.num_spare_rows==0:
|
||||||
addr_bits = self.convert_to_bin(random_value,True)
|
random_value = random.randint(0, (2 ** self.addr_size) - 1)
|
||||||
|
else:
|
||||||
|
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
|
||||||
|
addr_bits = self.convert_to_bin(random_value, True)
|
||||||
return addr_bits
|
return addr_bits
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
|
|
@ -296,37 +301,36 @@ class functional(simulation):
|
||||||
# Used for write masks since they should be writing to previously written addresses
|
# Used for write masks since they should be writing to previously written addresses
|
||||||
addr = random.choice(list(self.stored_words.keys()))
|
addr = random.choice(list(self.stored_words.keys()))
|
||||||
word = self.stored_words[addr]
|
word = self.stored_words[addr]
|
||||||
return (addr,word)
|
return (addr, word)
|
||||||
|
|
||||||
def convert_to_bin(self,value,is_addr):
|
def convert_to_bin(self, value, is_addr):
|
||||||
""" Converts addr & word to usable binary values. """
|
""" Converts addr & word to usable binary values. """
|
||||||
new_value = str.replace(bin(value),"0b","")
|
new_value = str.replace(bin(value), "0b", "")
|
||||||
if(is_addr):
|
if(is_addr):
|
||||||
expected_value = self.addr_size
|
expected_value = self.addr_size
|
||||||
else:
|
else:
|
||||||
|
expected_value = self.word_size + self.num_spare_cols
|
||||||
expected_value = self.word_size
|
for i in range(expected_value - len(new_value)):
|
||||||
for i in range (expected_value - len(new_value)):
|
|
||||||
new_value = "0" + new_value
|
new_value = "0" + new_value
|
||||||
|
|
||||||
#print("Binary Conversion: {} to {}".format(value, new_value))
|
# print("Binary Conversion: {} to {}".format(value, new_value))
|
||||||
return new_value
|
return new_value
|
||||||
|
|
||||||
def write_functional_stimulus(self):
|
def write_functional_stimulus(self):
|
||||||
""" Writes SPICE stimulus. """
|
""" Writes SPICE stimulus. """
|
||||||
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
||||||
self.sf = open(temp_stim,"w")
|
self.sf = open(temp_stim, "w")
|
||||||
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
||||||
self.stim = stimuli(self.sf,self.corner)
|
self.stim = stimuli(self.sf, self.corner)
|
||||||
|
|
||||||
#Write include statements
|
# Write include statements
|
||||||
self.stim.write_include(self.sp_file)
|
self.stim.write_include(self.sp_file)
|
||||||
|
|
||||||
#Write Vdd/Gnd statements
|
# Write Vdd/Gnd statements
|
||||||
self.sf.write("\n* Global Power Supplies\n")
|
self.sf.write("\n* Global Power Supplies\n")
|
||||||
self.stim.write_supply()
|
self.stim.write_supply()
|
||||||
|
|
||||||
#Instantiate the SRAM
|
# Instantiate the SRAM
|
||||||
self.sf.write("\n* Instantiation of the SRAM\n")
|
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||||
self.stim.inst_model(pins=self.pins,
|
self.stim.inst_model(pins=self.pins,
|
||||||
model_name=self.sram.name)
|
model_name=self.sram.name)
|
||||||
|
|
@ -334,7 +338,7 @@ class functional(simulation):
|
||||||
# Add load capacitance to each of the read ports
|
# Add load capacitance to each of the read ports
|
||||||
self.sf.write("\n* SRAM output loads\n")
|
self.sf.write("\n* SRAM output loads\n")
|
||||||
for port in self.read_ports:
|
for port in self.read_ports:
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
|
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))
|
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
|
||||||
|
|
||||||
|
|
@ -351,10 +355,10 @@ class functional(simulation):
|
||||||
for comment in self.fn_cycle_comments:
|
for comment in self.fn_cycle_comments:
|
||||||
self.sf.write("*{}\n".format(comment))
|
self.sf.write("*{}\n".format(comment))
|
||||||
|
|
||||||
# Generate data input bits
|
# Generate data input bits
|
||||||
self.sf.write("\n* Generation of data and address signals\n")
|
self.sf.write("\n* Generation of data and address signals\n")
|
||||||
for port in self.write_ports:
|
for port in self.write_ports:
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
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)
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
|
|
@ -367,10 +371,10 @@ class functional(simulation):
|
||||||
# Generate control signals
|
# Generate control signals
|
||||||
self.sf.write("\n * Generation of control signals\n")
|
self.sf.write("\n * Generation of control signals\n")
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05)
|
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
for port in self.readwrite_ports:
|
for port in self.readwrite_ports:
|
||||||
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05)
|
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||||
|
|
||||||
# Generate wmask bits
|
# Generate wmask bits
|
||||||
for port in self.write_ports:
|
for port in self.write_ports:
|
||||||
|
|
@ -383,6 +387,15 @@ class functional(simulation):
|
||||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period,
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period,
|
||||||
self.slew, 0.05)
|
self.slew, 0.05)
|
||||||
|
|
||||||
|
# Generate spare enable bits (for spare cols)
|
||||||
|
for port in self.write_ports:
|
||||||
|
if self.num_spare_cols:
|
||||||
|
self.sf.write("\n* Generation of spare enable signals\n")
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
sig_name = "SPARE_WEN{0}_{1} ".format(port, bit)
|
||||||
|
self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period,
|
||||||
|
self.slew, 0.05)
|
||||||
|
|
||||||
# Generate CLK signals
|
# Generate CLK signals
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
|
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
|
||||||
|
|
@ -396,11 +409,11 @@ class functional(simulation):
|
||||||
# Generate dout value measurements
|
# Generate dout value measurements
|
||||||
self.sf.write("\n * Generation of dout measurements\n")
|
self.sf.write("\n * Generation of dout measurements\n")
|
||||||
for (word, dout_port, eo_period, check) in self.read_check:
|
for (word, dout_port, eo_period, check) in self.read_check:
|
||||||
t_intital = eo_period - 0.01*self.period
|
t_intital = eo_period - 0.01 * self.period
|
||||||
t_final = eo_period + 0.01*self.period
|
t_final = eo_period + 0.01 * self.period
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
|
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
|
||||||
dout="{0}_{1}".format(dout_port,bit),
|
dout="{0}_{1}".format(dout_port, bit),
|
||||||
t_intital=t_intital,
|
t_intital=t_intital,
|
||||||
t_final=t_final)
|
t_final=t_final)
|
||||||
|
|
||||||
|
|
@ -430,7 +443,7 @@ class functional(simulation):
|
||||||
# Generate new graph every analysis as edges might change depending on test bit
|
# Generate new graph every analysis as edges might change depending on test bit
|
||||||
self.graph = graph_util.timing_graph()
|
self.graph = graph_util.timing_graph()
|
||||||
self.sram_spc_name = "X{}".format(self.sram.name)
|
self.sram_spc_name = "X{}".format(self.sram.name)
|
||||||
self.sram.build_graph(self.graph,self.sram_spc_name,self.pins)
|
self.sram.build_graph(self.graph, self.sram_spc_name, self.pins)
|
||||||
|
|
||||||
# FIXME: refactor to share with delay.py
|
# FIXME: refactor to share with delay.py
|
||||||
def set_internal_spice_names(self):
|
def set_internal_spice_names(self):
|
||||||
|
|
@ -438,17 +451,17 @@ class functional(simulation):
|
||||||
|
|
||||||
# For now, only testing these using first read port.
|
# For now, only testing these using first read port.
|
||||||
port = self.read_ports[0]
|
port = self.read_ports[0]
|
||||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||||
'{}{}_{}'.format(self.dout_name, port, 0).lower())
|
'{}{}_{}'.format(self.dout_name, port, 0).lower())
|
||||||
|
|
||||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||||
debug.info(2,"s_en name = {}".format(self.sen_name))
|
debug.info(2, "s_en name = {}".format(self.sen_name))
|
||||||
|
|
||||||
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
||||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
|
||||||
|
|
||||||
self.q_name,self.qbar_name = self.get_bit_name()
|
self.q_name, self.qbar_name = self.get_bit_name()
|
||||||
debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name))
|
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
|
||||||
|
|
||||||
def get_bit_name(self):
|
def get_bit_name(self):
|
||||||
""" Get a bit cell name """
|
""" Get a bit cell name """
|
||||||
|
|
@ -456,10 +469,10 @@ class functional(simulation):
|
||||||
storage_names = cell_inst.mod.get_storage_net_names()
|
storage_names = cell_inst.mod.get_storage_net_names()
|
||||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||||
"supported for characterization. Storage nets={}").format(storage_names))
|
"supported for characterization. Storage nets={}").format(storage_names))
|
||||||
q_name = cell_name+'.'+str(storage_names[0])
|
q_name = cell_name + '.' + str(storage_names[0])
|
||||||
qbar_name = cell_name+'.'+str(storage_names[1])
|
qbar_name = cell_name + '.' + str(storage_names[1])
|
||||||
|
|
||||||
return (q_name,qbar_name)
|
return (q_name, qbar_name)
|
||||||
|
|
||||||
# FIXME: refactor to share with delay.py
|
# FIXME: refactor to share with delay.py
|
||||||
def get_sen_name(self, paths):
|
def get_sen_name(self, paths):
|
||||||
|
|
@ -469,29 +482,28 @@ class functional(simulation):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sa_mods = factory.get_mods(OPTS.sense_amp)
|
sa_mods = factory.get_mods(OPTS.sense_amp)
|
||||||
# Any sense amp instantiated should be identical, any change to that
|
# Any sense amp instantiated should be identical, any change to that
|
||||||
# will require some identification to determine the mod desired.
|
# will require some identification to determine the mod desired.
|
||||||
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
|
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
|
||||||
enable_name = sa_mods[0].get_enable_name()
|
enable_name = sa_mods[0].get_enable_name()
|
||||||
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
|
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
|
||||||
return sen_name
|
return sen_name
|
||||||
|
|
||||||
# FIXME: refactor to share with delay.py
|
# FIXME: refactor to share with delay.py
|
||||||
def get_bl_name(self, paths, port):
|
def get_bl_name(self, paths, port):
|
||||||
"""Gets the signal name associated with the bitlines in the bank."""
|
"""Gets the signal name associated with the bitlines in the bank."""
|
||||||
|
|
||||||
cell_mod = factory.create(module_type=OPTS.bitcell)
|
cell_mod = factory.create(module_type=OPTS.bitcell)
|
||||||
cell_bl = cell_mod.get_bl_name(port)
|
cell_bl = cell_mod.get_bl_name(port)
|
||||||
cell_br = cell_mod.get_br_name(port)
|
cell_br = cell_mod.get_br_name(port)
|
||||||
|
|
||||||
bl_found = False
|
|
||||||
# Only a single path should contain a single s_en name. Anything else is an error.
|
# Only a single path should contain a single s_en name. Anything else is an error.
|
||||||
bl_names = []
|
bl_names = []
|
||||||
exclude_set = self.get_bl_name_search_exclusions()
|
exclude_set = self.get_bl_name_search_exclusions()
|
||||||
for int_net in [cell_bl, cell_br]:
|
for int_net in [cell_bl, cell_br]:
|
||||||
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
|
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
|
||||||
|
|
||||||
return bl_names[0], bl_names[1]
|
return bl_names[0], bl_names[1]
|
||||||
|
|
||||||
def get_bl_name_search_exclusions(self):
|
def get_bl_name_search_exclusions(self):
|
||||||
"""Gets the mods as a set which should be excluded while searching for name."""
|
"""Gets the mods as a set which should be excluded while searching for name."""
|
||||||
|
|
@ -500,9 +512,9 @@ class functional(simulation):
|
||||||
# so it makes the search awkward
|
# so it makes the search awkward
|
||||||
return set(factory.get_mods(OPTS.replica_bitline))
|
return set(factory.get_mods(OPTS.replica_bitline))
|
||||||
|
|
||||||
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
|
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
|
||||||
"""
|
"""
|
||||||
Finds a single alias for the int_net in given paths.
|
Finds a single alias for the int_net in given paths.
|
||||||
More or less hits cause an error
|
More or less hits cause an error
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -510,14 +522,14 @@ class functional(simulation):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
|
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
|
||||||
if net_found and len(aliases) >= 1:
|
if net_found and len(aliases) >= 1:
|
||||||
debug.error('Found multiple paths with {} net.'.format(int_net),1)
|
debug.error('Found multiple paths with {} net.'.format(int_net), 1)
|
||||||
elif len(aliases) > 1:
|
elif len(aliases) > 1:
|
||||||
debug.error('Found multiple {} nets in single path.'.format(int_net),1)
|
debug.error('Found multiple {} nets in single path.'.format(int_net), 1)
|
||||||
elif not net_found and len(aliases) == 1:
|
elif not net_found and len(aliases) == 1:
|
||||||
path_net_name = aliases[0]
|
path_net_name = aliases[0]
|
||||||
net_found = True
|
net_found = True
|
||||||
if not net_found:
|
if not net_found:
|
||||||
debug.error("Could not find {} net in timing paths.".format(int_net),1)
|
debug.error("Could not find {} net in timing paths.".format(int_net), 1)
|
||||||
|
|
||||||
return path_net_name
|
return path_net_name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ class lib:
|
||||||
""" 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
|
||||||
#self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8])
|
#self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8])
|
||||||
self.load_scales = np.array([0.25, 1, 8])
|
self.load_scales = np.array([0.25, 1, 4])
|
||||||
#self.load_scales = np.array([0.25, 1])
|
#self.load_scales = np.array([0.25, 1])
|
||||||
self.load = tech.spice["dff_in_cap"]
|
self.load = tech.spice["dff_in_cap"]
|
||||||
self.loads = self.load_scales*self.load
|
self.loads = self.load_scales*self.load
|
||||||
|
|
@ -66,25 +66,57 @@ class lib:
|
||||||
self.supply_voltages = OPTS.supply_voltages
|
self.supply_voltages = OPTS.supply_voltages
|
||||||
self.process_corners = OPTS.process_corners
|
self.process_corners = OPTS.process_corners
|
||||||
|
|
||||||
# Enumerate all possible corners
|
# Corner values
|
||||||
|
min_temperature = min(self.temperatures)
|
||||||
|
nom_temperature = tech.spice["nom_temperature"]
|
||||||
|
max_temperature = max(self.temperatures)
|
||||||
|
min_supply = min(self.supply_voltages)
|
||||||
|
nom_supply = tech.spice["nom_supply_voltage"]
|
||||||
|
max_supply = max(self.supply_voltages)
|
||||||
|
min_process = "FF"
|
||||||
|
nom_process = "TT"
|
||||||
|
max_process = "SS"
|
||||||
|
|
||||||
self.corners = []
|
self.corners = []
|
||||||
self.lib_files = []
|
self.lib_files = []
|
||||||
for proc in self.process_corners:
|
|
||||||
for temp in self.temperatures:
|
# Nominal corner
|
||||||
for volt in self.supply_voltages:
|
corner_set = set()
|
||||||
self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name,
|
nom_corner = (nom_process, nom_supply, nom_temperature)
|
||||||
proc,
|
corner_set.add(nom_corner)
|
||||||
volt,
|
if not OPTS.nominal_corner_only:
|
||||||
temp)
|
# Temperature corners
|
||||||
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
|
corner_set.add((nom_process, nom_supply, min_temperature))
|
||||||
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
|
corner_set.add((nom_process, nom_supply, max_temperature))
|
||||||
|
# Supply corners
|
||||||
|
corner_set.add((nom_process, min_supply, nom_temperature))
|
||||||
|
corner_set.add((nom_process, max_supply, nom_temperature))
|
||||||
|
# Process corners
|
||||||
|
corner_set.add((min_process, nom_supply, nom_temperature))
|
||||||
|
corner_set.add((max_process, nom_supply, nom_temperature))
|
||||||
|
|
||||||
|
# Enforce that nominal corner is the first to be characterized
|
||||||
|
self.add_corner(*nom_corner)
|
||||||
|
corner_set.remove(nom_corner)
|
||||||
|
for corner_tuple in corner_set:
|
||||||
|
self.add_corner(*corner_tuple)
|
||||||
|
|
||||||
|
def add_corner(self, proc, volt, temp):
|
||||||
|
self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name,
|
||||||
|
proc,
|
||||||
|
volt,
|
||||||
|
temp)
|
||||||
|
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
|
||||||
|
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
|
||||||
|
|
||||||
|
# A corner is a tuple of PVT
|
||||||
|
self.corners.append((proc, volt, temp))
|
||||||
|
self.lib_files.append(lib_name)
|
||||||
|
|
||||||
# A corner is a tuple of PVT
|
|
||||||
self.corners.append((proc, volt, temp))
|
|
||||||
self.lib_files.append(lib_name)
|
|
||||||
|
|
||||||
def characterize_corners(self):
|
def characterize_corners(self):
|
||||||
""" Characterize the list of corners. """
|
""" Characterize the list of corners. """
|
||||||
|
debug.info(1,"Characterizing corners: " + str(self.corners))
|
||||||
for (self.corner,lib_name) in zip(self.corners,self.lib_files):
|
for (self.corner,lib_name) in zip(self.corners,self.lib_files):
|
||||||
debug.info(1,"Corner: " + str(self.corner))
|
debug.info(1,"Corner: " + str(self.corner))
|
||||||
(self.process, self.voltage, self.temperature) = self.corner
|
(self.process, self.voltage, self.temperature) = self.corner
|
||||||
|
|
@ -149,17 +181,20 @@ class lib:
|
||||||
self.lib.write(" dont_touch : true;\n")
|
self.lib.write(" 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.
|
self.write_pg_pin()
|
||||||
|
|
||||||
|
#Build string of all control signals.
|
||||||
control_str = 'csb0' #assume at least 1 port
|
control_str = 'csb0' #assume at least 1 port
|
||||||
for i in range(1, self.total_port_num):
|
for i in range(1, self.total_port_num):
|
||||||
control_str += ' & csb{0}'.format(i)
|
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 : \"{0}\";\n".format(control_str))
|
# 'when' condition unnecessary when cs pin does not turn power to devices
|
||||||
|
# self.lib.write(" when : \"{0}\";\n".format(control_str))
|
||||||
self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"]))
|
self.lib.write(" 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(self.char_sram_results["leakage_power"]))
|
||||||
|
|
||||||
|
|
||||||
def write_units(self):
|
def write_units(self):
|
||||||
|
|
@ -208,6 +243,9 @@ class lib:
|
||||||
self.lib.write(" default_max_fanout : 4.0 ;\n")
|
self.lib.write(" default_max_fanout : 4.0 ;\n")
|
||||||
self.lib.write(" default_connection_class : universal ;\n\n")
|
self.lib.write(" default_connection_class : universal ;\n\n")
|
||||||
|
|
||||||
|
self.lib.write(" voltage_map ( VDD, {} );\n".format(tech.spice["nom_supply_voltage"]))
|
||||||
|
self.lib.write(" voltage_map ( GND, 0 );\n\n")
|
||||||
|
|
||||||
def create_list(self,values):
|
def create_list(self,values):
|
||||||
""" Helper function to create quoted, line wrapped list """
|
""" Helper function to create quoted, line wrapped list """
|
||||||
list_values = ", ".join(str(v) for v in values)
|
list_values = ", ".join(str(v) for v in values)
|
||||||
|
|
@ -484,42 +522,69 @@ class lib:
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
if port in self.read_ports:
|
if port in self.read_ports:
|
||||||
web_name = " & !web{0}".format(port)
|
web_name = " & !web{0}".format(port)
|
||||||
avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])
|
write1_power = np.mean(self.char_port_results[port]["write1_power"])
|
||||||
|
write0_power = np.mean(self.char_port_results[port]["write0_power"])
|
||||||
self.lib.write(" internal_power(){\n")
|
self.lib.write(" internal_power(){\n")
|
||||||
self.lib.write(" when : \"!csb{0} & clk{0}{1}\"; \n".format(port, web_name))
|
self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name))
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
self.lib.write(" values(\"{0:.6e}\");\n".format(write1_power))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
self.lib.write(" values(\"{0:.6e}\");\n".format(write0_power))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
# Disabled power.
|
||||||
|
disabled_write1_power = np.mean(self.char_port_results[port]["disabled_write1_power"])
|
||||||
|
disabled_write0_power = np.mean(self.char_port_results[port]["disabled_write0_power"])
|
||||||
|
self.lib.write(" internal_power(){\n")
|
||||||
|
self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name))
|
||||||
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write1_power))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
|
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write0_power))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
if port in self.read_ports:
|
if port in self.read_ports:
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
web_name = " & web{0}".format(port)
|
web_name = " & web{0}".format(port)
|
||||||
avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])
|
read1_power = np.mean(self.char_port_results[port]["read1_power"])
|
||||||
|
read0_power = np.mean(self.char_port_results[port]["read0_power"])
|
||||||
self.lib.write(" internal_power(){\n")
|
self.lib.write(" internal_power(){\n")
|
||||||
self.lib.write(" when : \"!csb{0} & !clk{0}{1}\"; \n".format(port, web_name))
|
self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name))
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
self.lib.write(" values(\"{0:.6e}\");\n".format(read1_power))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
self.lib.write(" values(\"{0:.6e}\");\n".format(read0_power))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
# Have 0 internal power when disabled, this will be represented as leakage power.
|
# Disabled power.
|
||||||
self.lib.write(" internal_power(){\n")
|
disabled_read1_power = np.mean(self.char_port_results[port]["disabled_read1_power"])
|
||||||
self.lib.write(" when : \"csb{0}\"; \n".format(port))
|
disabled_read0_power = np.mean(self.char_port_results[port]["disabled_read0_power"])
|
||||||
self.lib.write(" rise_power(scalar){\n")
|
self.lib.write(" internal_power(){\n")
|
||||||
self.lib.write(" values(\"0\");\n")
|
self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name))
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" rise_power(scalar){\n")
|
||||||
self.lib.write(" fall_power(scalar){\n")
|
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read1_power))
|
||||||
self.lib.write(" values(\"0\");\n")
|
self.lib.write(" }\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" fall_power(scalar){\n")
|
||||||
self.lib.write(" }\n")
|
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read0_power))
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
self.lib.write(" }\n")
|
||||||
|
|
||||||
|
def write_pg_pin(self):
|
||||||
|
self.lib.write(" pg_pin(vdd) {\n")
|
||||||
|
self.lib.write(" voltage_name : VDD;\n")
|
||||||
|
self.lib.write(" pg_type : primary_power;\n")
|
||||||
|
self.lib.write(" }\n\n")
|
||||||
|
self.lib.write(" pg_pin(gnd) {\n")
|
||||||
|
self.lib.write(" voltage_name : GND;\n")
|
||||||
|
self.lib.write(" pg_type : primary_ground;\n")
|
||||||
|
self.lib.write(" }\n\n")
|
||||||
|
|
||||||
def compute_delay(self):
|
def compute_delay(self):
|
||||||
"""Compute SRAM delays for current corner"""
|
"""Compute SRAM delays for current corner"""
|
||||||
self.d = delay(self.sram, self.sp_file, self.corner)
|
self.d = delay(self.sram, self.sp_file, self.corner)
|
||||||
|
|
@ -527,7 +592,10 @@ class lib:
|
||||||
char_results = self.d.analytical_delay(self.slews,self.loads)
|
char_results = self.d.analytical_delay(self.slews,self.loads)
|
||||||
self.char_sram_results, self.char_port_results = char_results
|
self.char_sram_results, self.char_port_results = char_results
|
||||||
else:
|
else:
|
||||||
probe_address = "1" * self.sram.addr_size
|
if (self.sram.num_spare_rows == 0):
|
||||||
|
probe_address = "1" * self.sram.addr_size
|
||||||
|
else:
|
||||||
|
probe_address = "0" + "1" * (self.sram.addr_size - 1)
|
||||||
probe_data = self.sram.word_size - 1
|
probe_data = self.sram.word_size - 1
|
||||||
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
|
self.char_sram_results, self.char_port_results = char_results
|
||||||
|
|
@ -589,17 +657,12 @@ class lib:
|
||||||
))
|
))
|
||||||
|
|
||||||
# information of checks
|
# information of checks
|
||||||
from hierarchy_design import total_drc_errors
|
# run it only the first time
|
||||||
from hierarchy_design import total_lvs_errors
|
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||||
DRC = 'skipped'
|
|
||||||
LVS = 'skipped'
|
|
||||||
if OPTS.check_lvsdrc:
|
|
||||||
DRC = str(total_drc_errors)
|
|
||||||
LVS = str(total_lvs_errors)
|
|
||||||
|
|
||||||
datasheet.write("{0},{1},".format(DRC, LVS))
|
|
||||||
# write area
|
# write area
|
||||||
datasheet.write(str(self.sram.width * self.sram.height)+',')
|
datasheet.write(str(self.sram.width * self.sram.height) + ',')
|
||||||
|
|
||||||
# write timing information for all ports
|
# write timing information for all ports
|
||||||
for port in self.all_ports:
|
for port in self.all_ports:
|
||||||
#din timings
|
#din timings
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,23 @@ class simulation():
|
||||||
self.word_size = self.sram.word_size
|
self.word_size = self.sram.word_size
|
||||||
self.addr_size = self.sram.addr_size
|
self.addr_size = self.sram.addr_size
|
||||||
self.write_size = self.sram.write_size
|
self.write_size = self.sram.write_size
|
||||||
|
self.num_spare_rows = self.sram.num_spare_rows
|
||||||
|
if not self.sram.num_spare_cols:
|
||||||
|
self.num_spare_cols = 0
|
||||||
|
else:
|
||||||
|
self.num_spare_cols = self.sram.num_spare_cols
|
||||||
self.sp_file = spfile
|
self.sp_file = spfile
|
||||||
|
|
||||||
self.all_ports = self.sram.all_ports
|
self.all_ports = self.sram.all_ports
|
||||||
self.readwrite_ports = self.sram.readwrite_ports
|
self.readwrite_ports = self.sram.readwrite_ports
|
||||||
self.read_ports = self.sram.read_ports
|
self.read_ports = self.sram.read_ports
|
||||||
self.write_ports = self.sram.write_ports
|
self.write_ports = self.sram.write_ports
|
||||||
|
self.words_per_row = self.sram.words_per_row
|
||||||
if self.write_size:
|
if self.write_size:
|
||||||
self.num_wmasks = int(self.word_size/self.write_size)
|
self.num_wmasks = int(self.word_size/self.write_size)
|
||||||
else:
|
else:
|
||||||
self.num_wmasks = 0
|
self.num_wmasks = 0
|
||||||
|
|
||||||
|
|
||||||
def set_corner(self,corner):
|
def set_corner(self,corner):
|
||||||
""" Set the corner values """
|
""" Set the corner values """
|
||||||
self.corner = corner
|
self.corner = corner
|
||||||
|
|
@ -59,10 +64,10 @@ class simulation():
|
||||||
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||||
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
||||||
abits=self.addr_size,
|
abits=self.addr_size,
|
||||||
dbits=self.word_size)
|
dbits=self.word_size + self.num_spare_cols)
|
||||||
debug.check(len(self.sram.pins) == len(self.pins),
|
debug.check(len(self.sram.pins) == len(self.pins),
|
||||||
"Number of pins generated for characterization \
|
"Number of pins generated for characterization \
|
||||||
do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
|
do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
|
||||||
self.pins))
|
self.pins))
|
||||||
#This is TODO once multiport control has been finalized.
|
#This is TODO once multiport control has been finalized.
|
||||||
#self.control_name = "CSB"
|
#self.control_name = "CSB"
|
||||||
|
|
@ -80,11 +85,13 @@ class simulation():
|
||||||
self.addr_value = {port:[] for port in self.all_ports}
|
self.addr_value = {port:[] for port in self.all_ports}
|
||||||
self.data_value = {port:[] for port in self.write_ports}
|
self.data_value = {port:[] for port in self.write_ports}
|
||||||
self.wmask_value = {port:[] for port in self.write_ports}
|
self.wmask_value = {port:[] for port in self.write_ports}
|
||||||
|
self.spare_wen_value = {port:[] for port in self.write_ports}
|
||||||
|
|
||||||
# Three dimensional list to handle each addr and data bits for each port over the number of checks
|
# Three dimensional list to handle each addr and data bits for each port over the number of checks
|
||||||
self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports}
|
self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports}
|
||||||
self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports}
|
self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports}
|
||||||
self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports}
|
self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports}
|
||||||
|
self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
|
||||||
|
|
||||||
# For generating comments in SPICE stimulus
|
# For generating comments in SPICE stimulus
|
||||||
self.cycle_comments = []
|
self.cycle_comments = []
|
||||||
|
|
@ -111,10 +118,10 @@ class simulation():
|
||||||
|
|
||||||
def add_data(self, data, port):
|
def add_data(self, data, port):
|
||||||
""" Add the array of data values """
|
""" Add the array of data values """
|
||||||
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.")
|
||||||
|
|
||||||
self.data_value[port].append(data)
|
self.data_value[port].append(data)
|
||||||
bit = self.word_size - 1
|
bit = self.word_size + self.num_spare_cols - 1
|
||||||
for c in data:
|
for c in data:
|
||||||
if c=="0":
|
if c=="0":
|
||||||
self.data_values[port][bit].append(0)
|
self.data_values[port][bit].append(0)
|
||||||
|
|
@ -124,7 +131,6 @@ class simulation():
|
||||||
debug.error("Non-binary data string",1)
|
debug.error("Non-binary data string",1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
||||||
|
|
||||||
def add_address(self, address, port):
|
def add_address(self, address, port):
|
||||||
""" Add the array of address values """
|
""" Add the array of address values """
|
||||||
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||||
|
|
@ -135,7 +141,7 @@ class simulation():
|
||||||
if c=="0":
|
if c=="0":
|
||||||
self.addr_values[port][bit].append(0)
|
self.addr_values[port][bit].append(0)
|
||||||
elif c=="1":
|
elif c=="1":
|
||||||
self.addr_values[port][bit].append(1)
|
self.addr_values[port][bit].append(1)
|
||||||
else:
|
else:
|
||||||
debug.error("Non-binary address string",1)
|
debug.error("Non-binary address string",1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
@ -156,7 +162,21 @@ class simulation():
|
||||||
debug.error("Non-binary wmask string", 1)
|
debug.error("Non-binary wmask string", 1)
|
||||||
bit -= 1
|
bit -= 1
|
||||||
|
|
||||||
|
def add_spare_wen(self, spare_wen, port):
|
||||||
|
""" Add the array of spare write enable values (for spare cols) """
|
||||||
|
debug.check(len(spare_wen) == self.num_spare_cols, "Invalid spare enable size.")
|
||||||
|
|
||||||
|
self.spare_wen_value[port].append(spare_wen)
|
||||||
|
bit = self.num_spare_cols - 1
|
||||||
|
for c in spare_wen:
|
||||||
|
if c == "0":
|
||||||
|
self.spare_wen_values[port][bit].append(0)
|
||||||
|
elif c == "1":
|
||||||
|
self.spare_wen_values[port][bit].append(1)
|
||||||
|
else:
|
||||||
|
debug.error("Non-binary spare enable signal string", 1)
|
||||||
|
bit -= 1
|
||||||
|
|
||||||
def add_write(self, comment, address, data, wmask, port):
|
def add_write(self, comment, address, data, wmask, port):
|
||||||
""" Add the control values for a write cycle. """
|
""" Add the control values for a write cycle. """
|
||||||
debug.check(port in self.write_ports,
|
debug.check(port in self.write_ports,
|
||||||
|
|
@ -172,7 +192,8 @@ class simulation():
|
||||||
self.add_control_one_port(port, "write")
|
self.add_control_one_port(port, "write")
|
||||||
self.add_data(data,port)
|
self.add_data(data,port)
|
||||||
self.add_address(address,port)
|
self.add_address(address,port)
|
||||||
self.add_wmask(wmask,port)
|
self.add_wmask(wmask,port)
|
||||||
|
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||||
|
|
||||||
#Add noops to all other ports.
|
#Add noops to all other ports.
|
||||||
for unselected_port in self.all_ports:
|
for unselected_port in self.all_ports:
|
||||||
|
|
@ -191,19 +212,20 @@ class simulation():
|
||||||
self.cycle_times.append(self.t_current)
|
self.cycle_times.append(self.t_current)
|
||||||
self.t_current += self.period
|
self.t_current += self.period
|
||||||
self.add_control_one_port(port, "read")
|
self.add_control_one_port(port, "read")
|
||||||
self.add_address(address, port)
|
self.add_address(address, port)
|
||||||
|
|
||||||
# If the port is also a readwrite then add
|
# If the port is also a readwrite then add
|
||||||
# the same value as previous cycle
|
# the same value as previous cycle
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
try:
|
try:
|
||||||
self.add_data(self.data_value[port][-1], port)
|
self.add_data(self.data_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_data("0"*self.word_size, port)
|
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||||
try:
|
try:
|
||||||
self.add_wmask(self.wmask_value[port][-1], port)
|
self.add_wmask(self.wmask_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_wmask("0"*self.num_wmasks, port)
|
self.add_wmask("0"*self.num_wmasks, port)
|
||||||
|
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||||
|
|
||||||
#Add noops to all other ports.
|
#Add noops to all other ports.
|
||||||
for unselected_port in self.all_ports:
|
for unselected_port in self.all_ports:
|
||||||
|
|
@ -234,6 +256,7 @@ class simulation():
|
||||||
self.add_data(data, port)
|
self.add_data(data, port)
|
||||||
self.add_address(address, port)
|
self.add_address(address, port)
|
||||||
self.add_wmask(wmask, port)
|
self.add_wmask(wmask, port)
|
||||||
|
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||||
|
|
||||||
def add_read_one_port(self, comment, address, port):
|
def add_read_one_port(self, comment, address, port):
|
||||||
""" Add the control values for a read cycle. Does not increment the period. """
|
""" Add the control values for a read cycle. Does not increment the period. """
|
||||||
|
|
@ -245,23 +268,24 @@ class simulation():
|
||||||
|
|
||||||
self.add_control_one_port(port, "read")
|
self.add_control_one_port(port, "read")
|
||||||
self.add_address(address, port)
|
self.add_address(address, port)
|
||||||
|
|
||||||
# If the port is also a readwrite then add
|
# If the port is also a readwrite then add
|
||||||
# the same value as previous cycle
|
# the same value as previous cycle
|
||||||
if port in self.write_ports:
|
if port in self.write_ports:
|
||||||
try:
|
try:
|
||||||
self.add_data(self.data_value[port][-1], port)
|
self.add_data(self.data_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_data("0"*self.word_size, port)
|
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||||
try:
|
try:
|
||||||
self.add_wmask(self.wmask_value[port][-1], port)
|
self.add_wmask(self.wmask_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_wmask("0"*self.num_wmasks, port)
|
self.add_wmask("0"*self.num_wmasks, port)
|
||||||
|
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||||
|
|
||||||
def add_noop_one_port(self, port):
|
def add_noop_one_port(self, port):
|
||||||
""" Add the control values for a noop to a single port. Does not increment the period. """
|
""" Add the control values for a noop to a single port. Does not increment the period. """
|
||||||
self.add_control_one_port(port, "noop")
|
self.add_control_one_port(port, "noop")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.add_address(self.addr_value[port][-1], port)
|
self.add_address(self.addr_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
|
|
@ -273,11 +297,29 @@ class simulation():
|
||||||
try:
|
try:
|
||||||
self.add_data(self.data_value[port][-1], port)
|
self.add_data(self.data_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_data("0"*self.word_size, port)
|
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||||
try:
|
try:
|
||||||
self.add_wmask(self.wmask_value[port][-1], port)
|
self.add_wmask(self.wmask_value[port][-1], port)
|
||||||
except:
|
except:
|
||||||
self.add_wmask("0"*self.num_wmasks, port)
|
self.add_wmask("0"*self.num_wmasks, port)
|
||||||
|
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||||
|
|
||||||
|
def add_noop_clock_one_port(self, port):
|
||||||
|
""" Add the control values for a noop to a single port. Increments the period. """
|
||||||
|
debug.info(2, 'Clock only on port {}'.format(port))
|
||||||
|
self.fn_cycle_comments.append('Clock only on port {}'.format(port))
|
||||||
|
self.append_cycle_comment(port, 'Clock only on port {}'.format(port))
|
||||||
|
|
||||||
|
self.cycle_times.append(self.t_current)
|
||||||
|
self.t_current += self.period
|
||||||
|
|
||||||
|
self.add_noop_one_port(port)
|
||||||
|
|
||||||
|
#Add noops to all other ports.
|
||||||
|
for unselected_port in self.all_ports:
|
||||||
|
if unselected_port != port:
|
||||||
|
self.add_noop_one_port(unselected_port)
|
||||||
|
|
||||||
|
|
||||||
def append_cycle_comment(self, port, comment):
|
def append_cycle_comment(self, port, comment):
|
||||||
"""Add comment to list to be printed in stimulus file"""
|
"""Add comment to list to be printed in stimulus file"""
|
||||||
|
|
@ -352,6 +394,11 @@ class simulation():
|
||||||
for port in write_index:
|
for port in write_index:
|
||||||
for bit in range(self.num_wmasks):
|
for bit in range(self.num_wmasks):
|
||||||
pin_names.append("WMASK{0}_{1}".format(port,bit))
|
pin_names.append("WMASK{0}_{1}".format(port,bit))
|
||||||
|
|
||||||
|
if self.num_spare_cols:
|
||||||
|
for port in write_index:
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
pin_names.append("SPARE_WEN{0}_{1}".format(port,bit))
|
||||||
|
|
||||||
for read_output in read_index:
|
for read_output in read_index:
|
||||||
for i in range(dbits):
|
for i in range(dbits):
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ class stimuli():
|
||||||
self.sf = stim_file
|
self.sf = stim_file
|
||||||
|
|
||||||
(self.process, self.voltage, self.temperature) = corner
|
(self.process, self.voltage, self.temperature) = corner
|
||||||
|
try:
|
||||||
|
self.device_libraries = tech.spice["fet_libraries"][self.process]
|
||||||
|
except:
|
||||||
|
debug.info(2, "Not using spice library")
|
||||||
self.device_models = tech.spice["fet_models"][self.process]
|
self.device_models = tech.spice["fet_models"][self.process]
|
||||||
|
|
||||||
self.sram_name = "Xsram"
|
self.sram_name = "Xsram"
|
||||||
|
|
@ -270,8 +274,17 @@ class stimuli():
|
||||||
|
|
||||||
def write_include(self, circuit):
|
def write_include(self, circuit):
|
||||||
"""Writes include statements, inputs are lists of model files"""
|
"""Writes include statements, inputs are lists of model files"""
|
||||||
|
|
||||||
includes = self.device_models + [circuit]
|
includes = self.device_models + [circuit]
|
||||||
self.sf.write("* {} process corner\n".format(self.process))
|
self.sf.write("* {} process corner\n".format(self.process))
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
libraries = self.device_libraries
|
||||||
|
for item in list(libraries):
|
||||||
|
if os.path.isfile(item[0]):
|
||||||
|
self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1]))
|
||||||
|
else:
|
||||||
|
debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0]))
|
||||||
|
|
||||||
for item in list(includes):
|
for item in list(includes):
|
||||||
if os.path.isfile(item):
|
if os.path.isfile(item):
|
||||||
self.sf.write(".include \"{0}\"\n".format(item))
|
self.sf.write(".include \"{0}\"\n".format(item))
|
||||||
|
|
@ -312,7 +325,9 @@ class stimuli():
|
||||||
OPTS.openram_temp)
|
OPTS.openram_temp)
|
||||||
valid_retcode=0
|
valid_retcode=0
|
||||||
else:
|
else:
|
||||||
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
||||||
|
# Measurements can't be made with a raw file set in ngspice
|
||||||
|
# -r {2}timing.raw
|
||||||
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
|
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
|
||||||
temp_stim,
|
temp_stim,
|
||||||
OPTS.openram_temp)
|
OPTS.openram_temp)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
from math import log
|
from math import log,ceil
|
||||||
import re
|
import re
|
||||||
|
|
||||||
class trim_spice():
|
class trim_spice():
|
||||||
|
|
@ -42,7 +42,7 @@ class trim_spice():
|
||||||
self.word_size = word_size
|
self.word_size = word_size
|
||||||
|
|
||||||
self.words_per_row = self.num_columns / self.word_size
|
self.words_per_row = self.num_columns / self.word_size
|
||||||
self.row_addr_size = int(log(self.num_rows, 2))
|
self.row_addr_size = ceil(log(self.num_rows, 2))
|
||||||
self.col_addr_size = int(log(self.words_per_row, 2))
|
self.col_addr_size = int(log(self.words_per_row, 2))
|
||||||
self.bank_addr_size = self.col_addr_size + self.row_addr_size
|
self.bank_addr_size = self.col_addr_size + self.row_addr_size
|
||||||
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
|
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
from vector import vector
|
||||||
|
import design
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
from tech import layer
|
||||||
|
|
||||||
|
|
||||||
|
class and2_dec(design.design):
|
||||||
|
"""
|
||||||
|
This is an AND with configurable drive strength.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, size=1, height=None, add_wells=True):
|
||||||
|
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
debug.info(1, "Creating and2_dec {}".format(name))
|
||||||
|
self.add_comment("size: {}".format(size))
|
||||||
|
self.size = size
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
self.create_insts()
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
|
self.nand = factory.create(module_type="nand2_dec",
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
self.inv = factory.create(module_type="inv_dec",
|
||||||
|
height=self.height,
|
||||||
|
size=self.size)
|
||||||
|
|
||||||
|
self.add_mod(self.nand)
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
|
self.width = self.nand.width + self.inv.width
|
||||||
|
self.height = self.nand.height
|
||||||
|
|
||||||
|
self.place_insts()
|
||||||
|
self.add_wires()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.route_supply_rails()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
self.add_pin("A", "INPUT")
|
||||||
|
self.add_pin("B", "INPUT")
|
||||||
|
self.add_pin("Z", "OUTPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
def create_insts(self):
|
||||||
|
self.nand_inst = self.add_inst(name="pand2_dec_nand",
|
||||||
|
mod=self.nand)
|
||||||
|
self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"])
|
||||||
|
|
||||||
|
self.inv_inst = self.add_inst(name="pand2_dec_inv",
|
||||||
|
mod=self.inv)
|
||||||
|
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_insts(self):
|
||||||
|
# Add NAND to the right
|
||||||
|
self.nand_inst.place(offset=vector(0, 0))
|
||||||
|
|
||||||
|
# Add INV to the right
|
||||||
|
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
|
||||||
|
|
||||||
|
def route_supply_rails(self):
|
||||||
|
""" Add vdd/gnd rails to the top, (middle), and bottom. """
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
for inst in [self.nand_inst, self.inv_inst]:
|
||||||
|
self.copy_layout_pin(inst, name)
|
||||||
|
else:
|
||||||
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, 0),
|
||||||
|
width=self.width)
|
||||||
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, self.height),
|
||||||
|
width=self.width)
|
||||||
|
|
||||||
|
def add_wires(self):
|
||||||
|
# nand Z to inv A
|
||||||
|
z1_pin = self.nand_inst.get_pin("Z")
|
||||||
|
a2_pin = self.inv_inst.get_pin("A")
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
|
||||||
|
else:
|
||||||
|
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
|
||||||
|
self.add_path(self.route_layer,
|
||||||
|
[z1_pin.center(), mid1_point, a2_pin.center()])
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
pin = self.inv_inst.get_pin("Z")
|
||||||
|
self.add_layout_pin_rect_center(text="Z",
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
for pin_name in ["A", "B"]:
|
||||||
|
pin = self.nand_inst.get_pin(pin_name)
|
||||||
|
self.add_layout_pin_rect_center(text=pin_name,
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
def get_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||||
|
"""Get the stage efforts of the A or B -> Z path"""
|
||||||
|
stage_effort_list = []
|
||||||
|
stage1_cout = self.inv.get_cin()
|
||||||
|
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
|
||||||
|
stage_effort_list.append(stage1)
|
||||||
|
last_stage_is_rise = stage1.is_rise
|
||||||
|
|
||||||
|
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
|
||||||
|
stage_effort_list.append(stage2)
|
||||||
|
|
||||||
|
return stage_effort_list
|
||||||
|
|
||||||
|
def get_cin(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nand.get_cin()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
from vector import vector
|
||||||
|
import design
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
from tech import layer
|
||||||
|
|
||||||
|
|
||||||
|
class and3_dec(design.design):
|
||||||
|
"""
|
||||||
|
This is an AND with configurable drive strength.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, size=1, height=None, add_wells=True):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
debug.info(1, "Creating and3_dec {}".format(name))
|
||||||
|
self.add_comment("size: {}".format(size))
|
||||||
|
self.size = size
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
self.create_insts()
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
|
self.nand = factory.create(module_type="nand3_dec",
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
self.inv = factory.create(module_type="inv_dec",
|
||||||
|
height=self.height,
|
||||||
|
size=self.size)
|
||||||
|
|
||||||
|
self.add_mod(self.nand)
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
|
|
||||||
|
self.width = self.nand.width + self.inv.width
|
||||||
|
self.height = self.nand.height
|
||||||
|
|
||||||
|
self.place_insts()
|
||||||
|
self.add_wires()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.route_supply_rails()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
self.add_pin("A", "INPUT")
|
||||||
|
self.add_pin("B", "INPUT")
|
||||||
|
self.add_pin("C", "INPUT")
|
||||||
|
self.add_pin("Z", "OUTPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
def create_insts(self):
|
||||||
|
self.nand_inst = self.add_inst(name="pand3_dec_nand",
|
||||||
|
mod=self.nand)
|
||||||
|
self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"])
|
||||||
|
|
||||||
|
self.inv_inst = self.add_inst(name="pand3_dec_inv",
|
||||||
|
mod=self.inv)
|
||||||
|
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_insts(self):
|
||||||
|
# Add NAND to the right
|
||||||
|
self.nand_inst.place(offset=vector(0, 0))
|
||||||
|
|
||||||
|
# Add INV to the right
|
||||||
|
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
|
||||||
|
|
||||||
|
def route_supply_rails(self):
|
||||||
|
""" Add vdd/gnd rails to the top, (middle), and bottom. """
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
for inst in [self.nand_inst, self.inv_inst]:
|
||||||
|
self.copy_layout_pin(inst, name)
|
||||||
|
else:
|
||||||
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, 0),
|
||||||
|
width=self.width)
|
||||||
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, self.height),
|
||||||
|
width=self.width)
|
||||||
|
|
||||||
|
def add_wires(self):
|
||||||
|
# nand Z to inv A
|
||||||
|
z1_pin = self.nand_inst.get_pin("Z")
|
||||||
|
a2_pin = self.inv_inst.get_pin("A")
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
|
||||||
|
else:
|
||||||
|
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
|
||||||
|
self.add_path(self.route_layer,
|
||||||
|
[z1_pin.center(), mid1_point, a2_pin.center()])
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
pin = self.inv_inst.get_pin("Z")
|
||||||
|
self.add_layout_pin_rect_center(text="Z",
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
for pin_name in ["A", "B", "C"]:
|
||||||
|
pin = self.nand_inst.get_pin(pin_name)
|
||||||
|
self.add_layout_pin_rect_center(text=pin_name,
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
def analytical_delay(self, corner, slew, load=0.0):
|
||||||
|
""" Calculate the analytical delay of DFF-> INV -> INV """
|
||||||
|
nand_delay = self.nand.analytical_delay(corner,
|
||||||
|
slew=slew,
|
||||||
|
load=self.inv.input_load())
|
||||||
|
inv_delay = self.inv.analytical_delay(corner,
|
||||||
|
slew=nand_delay.slew,
|
||||||
|
load=load)
|
||||||
|
return nand_delay + inv_delay
|
||||||
|
|
||||||
|
def get_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||||
|
"""Get the stage efforts of the A or B -> Z path"""
|
||||||
|
stage_effort_list = []
|
||||||
|
stage1_cout = self.inv.get_cin()
|
||||||
|
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
|
||||||
|
stage_effort_list.append(stage1)
|
||||||
|
last_stage_is_rise = stage1.is_rise
|
||||||
|
|
||||||
|
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
|
||||||
|
stage_effort_list.append(stage2)
|
||||||
|
|
||||||
|
return stage_effort_list
|
||||||
|
|
||||||
|
def get_cin(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nand.get_cin()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
from vector import vector
|
||||||
|
import design
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
from tech import layer
|
||||||
|
|
||||||
|
|
||||||
|
class and4_dec(design.design):
|
||||||
|
"""
|
||||||
|
This is an AND with configurable drive strength.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, size=1, height=None, add_wells=True):
|
||||||
|
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
debug.info(1, "Creating and4_dec {}".format(name))
|
||||||
|
self.add_comment("size: {}".format(size))
|
||||||
|
self.size = size
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.create_modules()
|
||||||
|
self.create_insts()
|
||||||
|
|
||||||
|
def create_modules(self):
|
||||||
|
self.nand = factory.create(module_type="nand4_dec",
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
self.inv = factory.create(module_type="inv_dec",
|
||||||
|
height=self.height,
|
||||||
|
size=self.size)
|
||||||
|
|
||||||
|
self.add_mod(self.nand)
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
|
|
||||||
|
self.width = self.nand.width + self.inv.width
|
||||||
|
self.height = self.nand.height
|
||||||
|
|
||||||
|
self.place_insts()
|
||||||
|
self.add_wires()
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.route_supply_rails()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
self.add_pin("A", "INPUT")
|
||||||
|
self.add_pin("B", "INPUT")
|
||||||
|
self.add_pin("C", "INPUT")
|
||||||
|
self.add_pin("D", "INPUT")
|
||||||
|
self.add_pin("Z", "OUTPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
def create_insts(self):
|
||||||
|
self.nand_inst = self.add_inst(name="pand4_dec_nand",
|
||||||
|
mod=self.nand)
|
||||||
|
self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"])
|
||||||
|
|
||||||
|
self.inv_inst = self.add_inst(name="pand4_dec_inv",
|
||||||
|
mod=self.inv)
|
||||||
|
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_insts(self):
|
||||||
|
# Add NAND to the right
|
||||||
|
self.nand_inst.place(offset=vector(0, 0))
|
||||||
|
|
||||||
|
# Add INV to the right
|
||||||
|
self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0))
|
||||||
|
|
||||||
|
def route_supply_rails(self):
|
||||||
|
""" Add vdd/gnd rails to the top, (middle), and bottom. """
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
for inst in [self.nand_inst, self.inv_inst]:
|
||||||
|
self.copy_layout_pin(inst, name)
|
||||||
|
else:
|
||||||
|
self.add_layout_pin_rect_center(text="gnd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, 0),
|
||||||
|
width=self.width)
|
||||||
|
self.add_layout_pin_rect_center(text="vdd",
|
||||||
|
layer=self.route_layer,
|
||||||
|
offset=vector(0.5 * self.width, self.height),
|
||||||
|
width=self.width)
|
||||||
|
|
||||||
|
def add_wires(self):
|
||||||
|
# nand Z to inv A
|
||||||
|
z1_pin = self.nand_inst.get_pin("Z")
|
||||||
|
a2_pin = self.inv_inst.get_pin("A")
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
|
||||||
|
else:
|
||||||
|
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
|
||||||
|
self.add_path(self.route_layer,
|
||||||
|
[z1_pin.center(), mid1_point, a2_pin.center()])
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
pin = self.inv_inst.get_pin("Z")
|
||||||
|
self.add_layout_pin_rect_center(text="Z",
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
for pin_name in ["A", "B", "C"]:
|
||||||
|
pin = self.nand_inst.get_pin(pin_name)
|
||||||
|
self.add_layout_pin_rect_center(text=pin_name,
|
||||||
|
layer=pin.layer,
|
||||||
|
offset=pin.center(),
|
||||||
|
width=pin.width(),
|
||||||
|
height=pin.height())
|
||||||
|
|
||||||
|
def analytical_delay(self, corner, slew, load=0.0):
|
||||||
|
""" Calculate the analytical delay of DFF-> INV -> INV """
|
||||||
|
nand_delay = self.nand.analytical_delay(corner,
|
||||||
|
slew=slew,
|
||||||
|
load=self.inv.input_load())
|
||||||
|
inv_delay = self.inv.analytical_delay(corner,
|
||||||
|
slew=nand_delay.slew,
|
||||||
|
load=load)
|
||||||
|
return nand_delay + inv_delay
|
||||||
|
|
||||||
|
def get_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||||
|
"""Get the stage efforts of the A or B -> Z path"""
|
||||||
|
stage_effort_list = []
|
||||||
|
stage1_cout = self.inv.get_cin()
|
||||||
|
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
|
||||||
|
stage_effort_list.append(stage1)
|
||||||
|
last_stage_is_rise = stage1.is_rise
|
||||||
|
|
||||||
|
stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise)
|
||||||
|
stage_effort_list.append(stage2)
|
||||||
|
|
||||||
|
return stage_effort_list
|
||||||
|
|
||||||
|
def get_cin(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nand.get_cin()
|
||||||
|
|
||||||
|
|
@ -5,21 +5,28 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import globals
|
|
||||||
import design
|
import design
|
||||||
from math import log
|
from tech import GDS, layer, spice, parameter
|
||||||
import design
|
from tech import cell_properties as props
|
||||||
from tech import GDS,layer,spice,parameter
|
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
class dff(design.design):
|
class dff(design.design):
|
||||||
"""
|
"""
|
||||||
Memory address flip-flop
|
Memory address flip-flop
|
||||||
"""
|
"""
|
||||||
|
if not props.dff.use_custom_ports:
|
||||||
|
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||||
|
type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||||
|
clk_pin = "clk"
|
||||||
|
else:
|
||||||
|
pin_names = props.dff.custom_port_list
|
||||||
|
type_list = props.dff.custom_type_list
|
||||||
|
clk_pin = props.dff.clk_pin
|
||||||
|
|
||||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
(width, height) = utils.get_libcell_size("dff",
|
||||||
type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
GDS["unit"],
|
||||||
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
layer["boundary"])
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
||||||
|
|
||||||
def __init__(self, name="dff"):
|
def __init__(self, name="dff"):
|
||||||
|
|
@ -54,7 +61,7 @@ class dff(design.design):
|
||||||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||||
return parameter["dff_clk_cin"]
|
return parameter["dff_clk_cin"]
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import design
|
||||||
|
from tech import GDS, layer, spice, parameter
|
||||||
|
import logical_effort
|
||||||
|
import utils
|
||||||
|
import debug
|
||||||
|
|
||||||
|
|
||||||
|
class inv_dec(design.design):
|
||||||
|
"""
|
||||||
|
INV for address decoders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_names = ["A", "Z", "vdd", "gnd"]
|
||||||
|
type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("inv_dec",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name="inv_dec", height=None):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.width = inv_dec.width
|
||||||
|
self.height = inv_dec.height
|
||||||
|
self.pin_map = inv_dec.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
|
||||||
|
def analytical_power(self, corner, load):
|
||||||
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
|
c_eff = self.calculate_effective_capacitance(load)
|
||||||
|
freq = spice["default_event_frequency"]
|
||||||
|
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
|
||||||
|
power_leak = spice["inv_leakage"]
|
||||||
|
|
||||||
|
total_power = self.return_power(power_dyn, power_leak)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
def calculate_effective_capacitance(self, load):
|
||||||
|
"""Computes effective capacitance. Results in fF"""
|
||||||
|
c_load = load
|
||||||
|
# In fF
|
||||||
|
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
|
||||||
|
|
||||||
|
return transition_prob * (c_load + c_para)
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
"""
|
||||||
|
Return the capacitance of the gate connection in generic capacitive
|
||||||
|
units relative to the minimum width of a transistor
|
||||||
|
"""
|
||||||
|
return self.nmos_size + self.pmos_size
|
||||||
|
|
||||||
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||||
|
"""
|
||||||
|
Returns an object representing the parameters for delay in tau units.
|
||||||
|
Optional is_rise refers to the input direction rise/fall.
|
||||||
|
Input inverted by this stage.
|
||||||
|
"""
|
||||||
|
parasitic_delay = 1
|
||||||
|
return logical_effort.logical_effort(self.name,
|
||||||
|
self.size,
|
||||||
|
self.input_load(),
|
||||||
|
cout,
|
||||||
|
parasitic_delay,
|
||||||
|
not inp_is_rise)
|
||||||
|
|
||||||
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
|
"""
|
||||||
|
Adds edges based on inputs/outputs.
|
||||||
|
Overrides base class function.
|
||||||
|
"""
|
||||||
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import design
|
||||||
|
from tech import GDS, layer, spice, parameter, drc
|
||||||
|
import logical_effort
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
|
class nand2_dec(design.design):
|
||||||
|
"""
|
||||||
|
2-input NAND decoder for address decoders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_names = ["A", "B", "Z", "vdd", "gnd"]
|
||||||
|
type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("nand2_dec",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name="nand2_dec", height=None):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.width = nand2_dec.width
|
||||||
|
self.height = nand2_dec.height
|
||||||
|
self.pin_map = nand2_dec.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
|
||||||
|
# FIXME: For now...
|
||||||
|
size = 1
|
||||||
|
self.size = size
|
||||||
|
self.nmos_size = 2 * size
|
||||||
|
self.pmos_size = parameter["beta"] * size
|
||||||
|
self.nmos_width = self.nmos_size * drc("minwidth_tx")
|
||||||
|
self.pmos_width = self.pmos_size * drc("minwidth_tx")
|
||||||
|
|
||||||
|
def analytical_power(self, corner, load):
|
||||||
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
|
c_eff = self.calculate_effective_capacitance(load)
|
||||||
|
freq = spice["default_event_frequency"]
|
||||||
|
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
|
||||||
|
power_leak = spice["nand2_leakage"]
|
||||||
|
|
||||||
|
total_power = self.return_power(power_dyn, power_leak)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
def calculate_effective_capacitance(self, load):
|
||||||
|
"""Computes effective capacitance. Results in fF"""
|
||||||
|
c_load = load
|
||||||
|
# In fF
|
||||||
|
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
|
||||||
|
transition_prob = 0.1875
|
||||||
|
return transition_prob * (c_load + c_para)
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nmos_size + self.pmos_size
|
||||||
|
|
||||||
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||||
|
"""
|
||||||
|
Returns an object representing the parameters for delay in tau units.
|
||||||
|
Optional is_rise refers to the input direction rise/fall.
|
||||||
|
Input inverted by this stage.
|
||||||
|
"""
|
||||||
|
parasitic_delay = 2
|
||||||
|
return logical_effort.logical_effort(self.name,
|
||||||
|
self.size,
|
||||||
|
self.input_load(),
|
||||||
|
cout,
|
||||||
|
parasitic_delay,
|
||||||
|
not inp_is_rise)
|
||||||
|
|
||||||
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
|
"""
|
||||||
|
Adds edges based on inputs/outputs.
|
||||||
|
Overrides base class function.
|
||||||
|
"""
|
||||||
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import design
|
||||||
|
from tech import GDS, layer, spice, parameter, drc
|
||||||
|
import logical_effort
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
|
class nand3_dec(design.design):
|
||||||
|
"""
|
||||||
|
3-input NAND decoder for address decoders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_names = ["A", "B", "C", "Z", "vdd", "gnd"]
|
||||||
|
type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("nand3_dec",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name="nand3_dec", height=None):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.width = nand3_dec.width
|
||||||
|
self.height = nand3_dec.height
|
||||||
|
self.pin_map = nand3_dec.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
|
||||||
|
# FIXME: For now...
|
||||||
|
size = 1
|
||||||
|
self.size = size
|
||||||
|
self.nmos_size = 2 * size
|
||||||
|
self.pmos_size = parameter["beta"] * size
|
||||||
|
self.nmos_width = self.nmos_size * drc("minwidth_tx")
|
||||||
|
self.pmos_width = self.pmos_size * drc("minwidth_tx")
|
||||||
|
|
||||||
|
def analytical_power(self, corner, load):
|
||||||
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
|
c_eff = self.calculate_effective_capacitance(load)
|
||||||
|
freq = spice["default_event_frequency"]
|
||||||
|
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
|
||||||
|
power_leak = spice["nand3_leakage"]
|
||||||
|
|
||||||
|
total_power = self.return_power(power_dyn, power_leak)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
def calculate_effective_capacitance(self, load):
|
||||||
|
"""Computes effective capacitance. Results in fF"""
|
||||||
|
c_load = load
|
||||||
|
# In fF
|
||||||
|
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
|
||||||
|
transition_prob = 0.1875
|
||||||
|
return transition_prob * (c_load + c_para)
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nmos_size + self.pmos_size
|
||||||
|
|
||||||
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||||
|
"""
|
||||||
|
Returns an object representing the parameters for delay in tau units.
|
||||||
|
Optional is_rise refers to the input direction rise/fall.
|
||||||
|
Input inverted by this stage.
|
||||||
|
"""
|
||||||
|
parasitic_delay = 2
|
||||||
|
return logical_effort.logical_effort(self.name,
|
||||||
|
self.size,
|
||||||
|
self.input_load(),
|
||||||
|
cout,
|
||||||
|
parasitic_delay,
|
||||||
|
not inp_is_rise)
|
||||||
|
|
||||||
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
|
"""
|
||||||
|
Adds edges based on inputs/outputs.
|
||||||
|
Overrides base class function.
|
||||||
|
"""
|
||||||
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import design
|
||||||
|
from tech import GDS, layer, spice, parameter, drc
|
||||||
|
import logical_effort
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
|
class nand4_dec(design.design):
|
||||||
|
"""
|
||||||
|
2-input NAND decoder for address decoders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"]
|
||||||
|
type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||||
|
|
||||||
|
(width, height) = utils.get_libcell_size("nand4_dec",
|
||||||
|
GDS["unit"],
|
||||||
|
layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"])
|
||||||
|
|
||||||
|
def __init__(self, name="nand4_dec", height=None):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
self.width = nand4_dec.width
|
||||||
|
self.height = nand4_dec.height
|
||||||
|
self.pin_map = nand4_dec.pin_map
|
||||||
|
self.add_pin_types(self.type_list)
|
||||||
|
|
||||||
|
# FIXME: For now...
|
||||||
|
size = 1
|
||||||
|
self.size = size
|
||||||
|
self.nmos_size = 2 * size
|
||||||
|
self.pmos_size = parameter["beta"] * size
|
||||||
|
self.nmos_width = self.nmos_size * drc("minwidth_tx")
|
||||||
|
self.pmos_width = self.pmos_size * drc("minwidth_tx")
|
||||||
|
|
||||||
|
def analytical_power(self, corner, load):
|
||||||
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
|
c_eff = self.calculate_effective_capacitance(load)
|
||||||
|
freq = spice["default_event_frequency"]
|
||||||
|
power_dyn = self.calc_dynamic_power(corner, c_eff, freq)
|
||||||
|
power_leak = spice["nand4_leakage"]
|
||||||
|
|
||||||
|
total_power = self.return_power(power_dyn, power_leak)
|
||||||
|
return total_power
|
||||||
|
|
||||||
|
def calculate_effective_capacitance(self, load):
|
||||||
|
"""Computes effective capacitance. Results in fF"""
|
||||||
|
c_load = load
|
||||||
|
# In fF
|
||||||
|
c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"])
|
||||||
|
transition_prob = 0.1875
|
||||||
|
return transition_prob * (c_load + c_para)
|
||||||
|
|
||||||
|
def input_load(self):
|
||||||
|
"""Return the relative input capacitance of a single input"""
|
||||||
|
return self.nmos_size + self.pmos_size
|
||||||
|
|
||||||
|
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||||
|
"""
|
||||||
|
Returns an object representing the parameters for delay in tau units.
|
||||||
|
Optional is_rise refers to the input direction rise/fall.
|
||||||
|
Input inverted by this stage.
|
||||||
|
"""
|
||||||
|
parasitic_delay = 2
|
||||||
|
return logical_effort.logical_effort(self.name,
|
||||||
|
self.size,
|
||||||
|
self.input_load(),
|
||||||
|
cout,
|
||||||
|
parasitic_delay,
|
||||||
|
not inp_is_rise)
|
||||||
|
|
||||||
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
|
"""
|
||||||
|
Adds edges based on inputs/outputs.
|
||||||
|
Overrides base class function.
|
||||||
|
"""
|
||||||
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
|
@ -8,7 +8,9 @@
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
import utils
|
import utils
|
||||||
|
from globals import OPTS
|
||||||
from tech import GDS,layer
|
from tech import GDS,layer
|
||||||
|
from tech import cell_properties as props
|
||||||
|
|
||||||
class write_driver(design.design):
|
class write_driver(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -18,10 +20,20 @@ class write_driver(design.design):
|
||||||
the technology library.
|
the technology library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pin_names = ["din", "bl", "br", "en", "vdd", "gnd"]
|
pin_names = [props.write_driver.pin.din,
|
||||||
|
props.write_driver.pin.bl,
|
||||||
|
props.write_driver.pin.br,
|
||||||
|
props.write_driver.pin.en,
|
||||||
|
props.write_driver.pin.vdd,
|
||||||
|
props.write_driver.pin.gnd]
|
||||||
|
|
||||||
type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
if not OPTS.netlist_only:
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||||
|
else:
|
||||||
|
(width,height) = (0,0)
|
||||||
|
pin_map = []
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
@ -32,6 +44,20 @@ class write_driver(design.design):
|
||||||
self.pin_map = write_driver.pin_map
|
self.pin_map = write_driver.pin_map
|
||||||
self.add_pin_types(self.type_list)
|
self.add_pin_types(self.type_list)
|
||||||
|
|
||||||
|
def get_bl_names(self):
|
||||||
|
return props.write_driver.pin.bl
|
||||||
|
|
||||||
|
def get_br_names(self):
|
||||||
|
return props.write_driver.pin.br
|
||||||
|
|
||||||
|
@property
|
||||||
|
def din_name(self):
|
||||||
|
return props.write_driver.pin.din
|
||||||
|
|
||||||
|
@property
|
||||||
|
def en_name(self):
|
||||||
|
return props.write_driver.pin.en
|
||||||
|
|
||||||
def get_w_en_cin(self):
|
def get_w_en_cin(self):
|
||||||
"""Get the relative capacitance of a single input"""
|
"""Get the relative capacitance of a single input"""
|
||||||
# This is approximated from SCMOS. It has roughly 5 3x transistor gates.
|
# This is approximated from SCMOS. It has roughly 5 3x transistor gates.
|
||||||
|
|
@ -26,6 +26,9 @@ def check(check, str):
|
||||||
log("ERROR: file {0}: line {1}: {2}\n".format(
|
log("ERROR: file {0}: line {1}: {2}\n".format(
|
||||||
os.path.basename(filename), line_number, str))
|
os.path.basename(filename), line_number, str))
|
||||||
|
|
||||||
|
if globals.OPTS.debug_level > 0:
|
||||||
|
import pdb
|
||||||
|
pdb.set_trace()
|
||||||
assert 0
|
assert 0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,6 +40,9 @@ def error(str, return_value=0):
|
||||||
log("ERROR: file {0}: line {1}: {2}\n".format(
|
log("ERROR: file {0}: line {1}: {2}\n".format(
|
||||||
os.path.basename(filename), line_number, str))
|
os.path.basename(filename), line_number, str))
|
||||||
|
|
||||||
|
if globals.OPTS.debug_level > 0 and return_value != 0:
|
||||||
|
import pdb
|
||||||
|
pdb.set_trace()
|
||||||
assert return_value == 0
|
assert return_value == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,10 @@ import debug
|
||||||
from drc_value import *
|
from drc_value import *
|
||||||
from drc_lut import *
|
from drc_lut import *
|
||||||
|
|
||||||
class design_rules():
|
|
||||||
"""
|
class design_rules(dict):
|
||||||
This is a class that implements the design rules structures.
|
"""
|
||||||
|
This is a class that implements the design rules structures.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.tech_name = name
|
self.tech_name = name
|
||||||
|
|
@ -43,6 +44,22 @@ class design_rules():
|
||||||
else:
|
else:
|
||||||
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)
|
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.rules.keys()
|
||||||
|
|
||||||
|
def add_layer(self, name, width, spacing, area=0):
|
||||||
|
# Minimum width
|
||||||
|
self.add("minwidth_{}".format(name), width)
|
||||||
|
# Minimum spacing (could be a table too)
|
||||||
|
self.add("{0}_to_{0}".format(name), spacing)
|
||||||
|
# Minimum area
|
||||||
|
self.add("minarea_{}".format(name), area)
|
||||||
|
|
||||||
|
def add_enclosure(self, name, layer, enclosure, extension=None):
|
||||||
|
self.add("{0}_enclose_{1}".format(name, layer), enclosure)
|
||||||
|
# Reserved for asymmetric enclosures
|
||||||
|
if extension:
|
||||||
|
self.add("{0}_extend_{1}".format(name, layer), extension)
|
||||||
|
else:
|
||||||
|
self.add("{0}_extend_{1}".format(name, layer), enclosure)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
|
|
||||||
|
|
||||||
class drc_lut():
|
class drc_lut():
|
||||||
"""
|
"""
|
||||||
Implement a lookup table of rules.
|
Implement a lookup table of rules.
|
||||||
Each element is a tuple with the last value being the rule.
|
Each element is a tuple with the last value being the rule.
|
||||||
It searches through backwards until all of the key values are
|
It searches through backwards until all of the key values are
|
||||||
met and returns the rule value.
|
met and returns the rule value.
|
||||||
|
|
@ -31,7 +32,6 @@ class drc_lut():
|
||||||
for table_key in sorted(self.table.keys(), reverse=True):
|
for table_key in sorted(self.table.keys(), reverse=True):
|
||||||
if self.match(key, table_key):
|
if self.match(key, table_key):
|
||||||
return self.table[table_key]
|
return self.table[table_key]
|
||||||
|
|
||||||
|
|
||||||
def match(self, key1, key2):
|
def match(self, key1, key2):
|
||||||
"""
|
"""
|
||||||
|
|
@ -39,8 +39,8 @@ class drc_lut():
|
||||||
(i.e. return false if key1<key2 for any pair.)
|
(i.e. return false if key1<key2 for any pair.)
|
||||||
"""
|
"""
|
||||||
# If any one pair is less than, return False
|
# If any one pair is less than, return False
|
||||||
debug.check(len(key1)==len(key2),"Comparing invalid key lengths.")
|
debug.check(len(key1) == len(key2), "Comparing invalid key lengths.")
|
||||||
for k1,k2 in zip(key1,key2):
|
for k1, k2 in zip(key1, key2):
|
||||||
if k1 < k2:
|
if k1 < k2:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class drc_value():
|
class drc_value():
|
||||||
"""
|
"""
|
||||||
A single DRC value.
|
A single DRC value.
|
||||||
"""
|
"""
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ word_size = 32
|
||||||
num_words = 128
|
num_words = 128
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [5.0]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
@ -9,6 +10,3 @@ temperatures = [25]
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
output_name = "sram_{0}_{1}_{2}".format(word_size, num_words, tech_name)
|
output_name = "sram_{0}_{1}_{2}".format(word_size, num_words, tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ num_r_ports = 1
|
||||||
num_w_ports = 0
|
num_w_ports = 0
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [5.0]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
@ -17,7 +18,3 @@ output_path = "temp"
|
||||||
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,
|
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,
|
||||||
num_words,
|
num_words,
|
||||||
tech_name)
|
tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
word_size = 4
|
||||||
|
num_words = 64
|
||||||
|
words_per_row = 2
|
||||||
|
|
||||||
|
num_rw_ports = 1
|
||||||
|
num_r_ports = 0
|
||||||
|
num_w_ports = 0
|
||||||
|
|
||||||
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
|
process_corners = ["TT"]
|
||||||
|
supply_voltages = [5.0]
|
||||||
|
temperatures = [25]
|
||||||
|
|
||||||
|
# route_supplies = True
|
||||||
|
check_lvsdrc = True
|
||||||
|
|
||||||
|
output_path = "temp"
|
||||||
|
output_name = "sram_1rw_{0}_{1}_{2}".format(word_size,
|
||||||
|
num_words,
|
||||||
|
tech_name)
|
||||||
|
|
@ -6,6 +6,7 @@ num_r_ports = 1
|
||||||
num_w_ports = 1
|
num_w_ports = 1
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [5.0]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
@ -18,6 +19,3 @@ output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size,
|
||||||
num_words,
|
num_words,
|
||||||
tech_name)
|
tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ word_size = 2
|
||||||
num_words = 16
|
num_words = 16
|
||||||
|
|
||||||
tech_name = "freepdk45"
|
tech_name = "freepdk45"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [1.0]
|
supply_voltages = [1.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ word_size = 2
|
||||||
num_words = 16
|
num_words = 16
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [5.0]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
@ -14,6 +15,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
|
||||||
num_words,
|
num_words,
|
||||||
tech_name)
|
tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ word_size = 64
|
||||||
num_words = 1024
|
num_words = 1024
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [ 5.0 ]
|
supply_voltages = [ 5.0 ]
|
||||||
temperatures = [ 25 ]
|
temperatures = [ 25 ]
|
||||||
|
|
@ -10,7 +11,3 @@ output_path = "temp"
|
||||||
output_name = "sram_{0}_{1}_{2}".format(word_size,
|
output_name = "sram_{0}_{1}_{2}".format(word_size,
|
||||||
num_words,
|
num_words,
|
||||||
tech_name)
|
tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ word_size = 16
|
||||||
num_words = 256
|
num_words = 256
|
||||||
|
|
||||||
tech_name = "scn4m_subm"
|
tech_name = "scn4m_subm"
|
||||||
|
nominal_corners_only = False
|
||||||
process_corners = ["TT"]
|
process_corners = ["TT"]
|
||||||
supply_voltages = [3.3]
|
supply_voltages = [5.0]
|
||||||
temperatures = [25]
|
temperatures = [25]
|
||||||
|
|
||||||
output_path = "temp"
|
output_path = "temp"
|
||||||
|
|
@ -11,6 +12,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
|
||||||
num_words,
|
num_words,
|
||||||
tech_name)
|
tech_name)
|
||||||
|
|
||||||
drc_name = "magic"
|
|
||||||
lvs_name = "netgen"
|
|
||||||
pex_name = "magic"
|
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ class Gds2reader:
|
||||||
self.fileHandle = None
|
self.fileHandle = None
|
||||||
self.layoutObject = layoutObject
|
self.layoutObject = layoutObject
|
||||||
self.debugToTerminal=debugToTerminal
|
self.debugToTerminal=debugToTerminal
|
||||||
|
|
||||||
#do we dump debug data to the screen
|
#do we dump debug data to the screen
|
||||||
|
|
||||||
def print64AsBinary(self,number):
|
def print64AsBinary(self,number):
|
||||||
for index in range(0,64):
|
for index in range(0,64):
|
||||||
print((number>>(63-index))&0x1,eol='')
|
print((number>>(63-index))&0x1,eol='')
|
||||||
print("\n")
|
print("\n")
|
||||||
|
|
||||||
def stripNonASCII(self,bytestring):
|
def stripNonASCII(self,bytestring):
|
||||||
string = bytestring.decode('utf-8')
|
string = bytestring.decode('utf-8')
|
||||||
return string
|
return string
|
||||||
|
|
@ -29,20 +29,20 @@ class Gds2reader:
|
||||||
#(1)sign (7)exponent (56)mantissa
|
#(1)sign (7)exponent (56)mantissa
|
||||||
#exponent is excess 64, mantissa has no implied 1
|
#exponent is excess 64, mantissa has no implied 1
|
||||||
#a normal IEEE double is like this:
|
#a normal IEEE double is like this:
|
||||||
#(1)sign (11)exponent (52)mantissa
|
#(1)sign (11)exponent (52)mantissa
|
||||||
data = struct.unpack('>q',ibmData)[0]
|
data = struct.unpack('>q',ibmData)[0]
|
||||||
sign = (data >> 63)&0x01
|
sign = (data >> 63)&0x01
|
||||||
exponent = (data >> 56) & 0x7f
|
exponent = (data >> 56) & 0x7f
|
||||||
mantissa = data<<8 #chop off sign and exponent
|
mantissa = data<<8 #chop off sign and exponent
|
||||||
|
|
||||||
if mantissa == 0:
|
if mantissa == 0:
|
||||||
newFloat = 0.0
|
newFloat = 0.0
|
||||||
else:
|
else:
|
||||||
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
||||||
#re normalize
|
#re normalize
|
||||||
while mantissa & 0x8000000000000000 == 0:
|
while mantissa & 0x8000000000000000 == 0:
|
||||||
mantissa<<=1
|
mantissa<<=1
|
||||||
exponent-=1
|
exponent-=1
|
||||||
mantissa<<=1 #remove the assumed high bit
|
mantissa<<=1 #remove the assumed high bit
|
||||||
exponent-=1
|
exponent-=1
|
||||||
#check for underflow error -- should handle these properly!
|
#check for underflow error -- should handle these properly!
|
||||||
|
|
@ -56,7 +56,7 @@ class Gds2reader:
|
||||||
#convert back to double
|
#convert back to double
|
||||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||||
return newFloat
|
return newFloat
|
||||||
|
|
||||||
def ieeeFloatCheck(self,aFloat):
|
def ieeeFloatCheck(self,aFloat):
|
||||||
asciiDouble = struct.pack('>d',aFloat)
|
asciiDouble = struct.pack('>d',aFloat)
|
||||||
data = struct.unpack('>q',asciiDouble)[0]
|
data = struct.unpack('>q',asciiDouble)[0]
|
||||||
|
|
@ -70,12 +70,12 @@ class Gds2reader:
|
||||||
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
||||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||||
print("Check:"+str(newFloat))
|
print("Check:"+str(newFloat))
|
||||||
|
|
||||||
def readNextRecord(self):
|
def readNextRecord(self):
|
||||||
global offset
|
global offset
|
||||||
recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record
|
recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record
|
||||||
if len(recordLengthAscii)==0:
|
if len(recordLengthAscii)==0:
|
||||||
return
|
return
|
||||||
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
|
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
|
||||||
offset_int = int(recordLength[0]) # extract length
|
offset_int = int(recordLength[0]) # extract length
|
||||||
offset += offset_int # count offset
|
offset += offset_int # count offset
|
||||||
|
|
@ -96,20 +96,20 @@ class Gds2reader:
|
||||||
else:
|
else:
|
||||||
print("Invalid GDSII Header")
|
print("Invalid GDSII Header")
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
#read records until we hit the UNITS section... this is the last part of the header
|
#read records until we hit the UNITS section... this is the last part of the header
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
idBits = record[0:2]
|
idBits = record[0:2]
|
||||||
## Modified Date
|
## Modified Date
|
||||||
if idBits==b'\x01\x02' and len(record)==26:
|
if idBits==b'\x01\x02' and len(record)==26:
|
||||||
modYear = struct.unpack(">h",record[2:4])[0]
|
modYear = struct.unpack(">h",record[2:4])[0]
|
||||||
modMonth = struct.unpack(">h",record[4:6])[0]
|
modMonth = struct.unpack(">h",record[4:6])[0]
|
||||||
modDay = struct.unpack(">h",record[6:8])[0]
|
modDay = struct.unpack(">h",record[6:8])[0]
|
||||||
modHour = struct.unpack(">h",record[8:10])[0]
|
modHour = struct.unpack(">h",record[8:10])[0]
|
||||||
modMinute = struct.unpack(">h",record[10:12])[0]
|
modMinute = struct.unpack(">h",record[10:12])[0]
|
||||||
modSecond = struct.unpack(">h",record[12:14])[0]
|
modSecond = struct.unpack(">h",record[12:14])[0]
|
||||||
lastAccessYear = struct.unpack(">h",record[14:16])[0]
|
lastAccessYear = struct.unpack(">h",record[14:16])[0]
|
||||||
lastAccessMonth = struct.unpack(">h",record[16:18])[0]
|
lastAccessMonth = struct.unpack(">h",record[16:18])[0]
|
||||||
lastAccessDay = struct.unpack(">h",record[18:20])[0]
|
lastAccessDay = struct.unpack(">h",record[18:20])[0]
|
||||||
lastAccessHour = struct.unpack(">h",record[20:22])[0]
|
lastAccessHour = struct.unpack(">h",record[20:22])[0]
|
||||||
|
|
@ -164,17 +164,19 @@ class Gds2reader:
|
||||||
print("Mask: "+mask)
|
print("Mask: "+mask)
|
||||||
elif(idBits==b'\x03\x05'): #this is also wrong b/c python doesn't natively have an 8 byte float
|
elif(idBits==b'\x03\x05'): #this is also wrong b/c python doesn't natively have an 8 byte float
|
||||||
userUnits=self.ieeeDoubleFromIbmData(record[2:10])
|
userUnits=self.ieeeDoubleFromIbmData(record[2:10])
|
||||||
dbUnits=self.ieeeDoubleFromIbmData
|
dbUnits=self.ieeeDoubleFromIbmData(record[10:18])
|
||||||
self.layoutObject.info["units"] = (userUnits,dbUnits)
|
self.layoutObject.info["units"] = (userUnits,dbUnits)
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("Units: 1 user unit="+str(userUnits)+" database units, 1 database unit="+str(dbUnits)+" meters.")
|
print("Units: 1 user unit="+str(userUnits)+" database units, 1 database unit="+str(dbUnits)+" meters.")
|
||||||
break;
|
break;
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("End of GDSII Header Found")
|
print("End of GDSII Header Found")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def readBoundary(self):
|
def readBoundary(self):
|
||||||
##reads in a boundary type structure = a filled polygon
|
##reads in a boundary type structure = a filled polygon
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginBoundary")
|
||||||
thisBoundary=GdsBoundary()
|
thisBoundary=GdsBoundary()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -196,16 +198,11 @@ class Gds2reader:
|
||||||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||||
elif(idBits==b'\x16\x02'): #Purpose
|
elif(idBits==b'\x0E\x02'): #Purpose DATATYPE
|
||||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||||
thisBoundary.purposeLayer=purposeLayer
|
thisBoundary.purposeLayer=purposeLayer
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||||
elif(idBits==b'\x0E\x02'): #DataType
|
|
||||||
dataType = struct.unpack(">h",record[2:4])[0]
|
|
||||||
thisBoundary.dataType=dataType
|
|
||||||
if(self.debugToTerminal==1):
|
|
||||||
print("\t\t\tData Type: "+str(dataType))
|
|
||||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||||
numDataPoints = len(record)-2 #packed as XY coordinates 4 bytes each
|
numDataPoints = len(record)-2 #packed as XY coordinates 4 bytes each
|
||||||
thisBoundary.coordinates=[]
|
thisBoundary.coordinates=[]
|
||||||
|
|
@ -216,10 +213,15 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndBoundary")
|
||||||
break;
|
break;
|
||||||
return thisBoundary
|
return thisBoundary
|
||||||
|
|
||||||
def readPath(self): #reads in a path structure
|
def readPath(self): #reads in a path structure
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginPath")
|
||||||
|
|
||||||
thisPath=GdsPath()
|
thisPath=GdsPath()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -243,7 +245,7 @@ class Gds2reader:
|
||||||
print("\t\t\tDrawing Layer: "+str(drawingLayer))
|
print("\t\t\tDrawing Layer: "+str(drawingLayer))
|
||||||
elif(idBits==b'\x16\x02'): #Purpose
|
elif(idBits==b'\x16\x02'): #Purpose
|
||||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||||
thisPath.purposeLayer=purposeLayer
|
thisPath.purposeLayer=purposeLayer
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||||
elif(idBits==b'\x21\x02'): #Path type
|
elif(idBits==b'\x21\x02'): #Path type
|
||||||
|
|
@ -251,6 +253,11 @@ class Gds2reader:
|
||||||
thisPath.pathType=pathType
|
thisPath.pathType=pathType
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tPath Type: "+str(pathType))
|
print("\t\t\tPath Type: "+str(pathType))
|
||||||
|
elif(idBits==b'\x0E\x02'): #Data type
|
||||||
|
dataType = struct.unpack(">h",record[2:4])[0]
|
||||||
|
thisPath.dataType=dataType
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tData Type: "+str(dataType))
|
||||||
elif(idBits==b'\x0F\x03'): #Path width
|
elif(idBits==b'\x0F\x03'): #Path width
|
||||||
pathWidth = struct.unpack(">i",record[2:6])[0]
|
pathWidth = struct.unpack(">i",record[2:6])[0]
|
||||||
thisPath.pathWidth=pathWidth
|
thisPath.pathWidth=pathWidth
|
||||||
|
|
@ -266,10 +273,15 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndPath")
|
||||||
break;
|
break;
|
||||||
return thisPath
|
return thisPath
|
||||||
|
|
||||||
def readSref(self): #reads in a reference to another structure
|
def readSref(self): #reads in a reference to another structure
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginSref")
|
||||||
|
|
||||||
thisSref=GdsSref()
|
thisSref=GdsSref()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -306,7 +318,7 @@ class Gds2reader:
|
||||||
print("\t\t\tMagnification:"+str(magFactor))
|
print("\t\t\tMagnification:"+str(magFactor))
|
||||||
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
||||||
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
||||||
thisSref.rotateAngle=rotateAngle
|
thisSref.rotateAngle=rotateAngle
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
||||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||||
|
|
@ -317,10 +329,15 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndSref")
|
||||||
break;
|
break;
|
||||||
return thisSref
|
return thisSref
|
||||||
|
|
||||||
def readAref(self): #an array of references
|
def readAref(self): #an array of references
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginAref")
|
||||||
|
|
||||||
thisAref = GdsAref()
|
thisAref = GdsAref()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -357,7 +374,7 @@ class Gds2reader:
|
||||||
print("\t\t\tMagnification:"+str(magFactor))
|
print("\t\t\tMagnification:"+str(magFactor))
|
||||||
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
||||||
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
||||||
thisAref.rotateAngle=rotateAngle
|
thisAref.rotateAngle=rotateAngle
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
||||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||||
|
|
@ -372,11 +389,15 @@ class Gds2reader:
|
||||||
print("\t\t\t\tArray Width: "+str(rightMostX-topLeftX))
|
print("\t\t\t\tArray Width: "+str(rightMostX-topLeftX))
|
||||||
print("\t\t\t\tArray Height: "+str(topLeftY-bottomMostY))
|
print("\t\t\t\tArray Height: "+str(topLeftY-bottomMostY))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndAref")
|
||||||
break;
|
break;
|
||||||
return thisAref
|
return thisAref
|
||||||
|
|
||||||
def readText(self):
|
def readText(self):
|
||||||
##reads in a text structure
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginText")
|
||||||
|
|
||||||
thisText=GdsText()
|
thisText=GdsText()
|
||||||
while 1:
|
while 1:
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
|
|
@ -398,9 +419,9 @@ class Gds2reader:
|
||||||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||||
elif(idBits==b'\x16\x02'): #Purpose
|
elif(idBits==b'\x16\x02'): #Purpose TEXTTYPE
|
||||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||||
thisText.purposeLayer=purposeLayer
|
thisText.purposeLayer=purposeLayer
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||||
elif(idBits==b'\x1A\x01'): #Transformation
|
elif(idBits==b'\x1A\x01'): #Transformation
|
||||||
|
|
@ -472,10 +493,15 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tText String: "+textString)
|
print("\t\t\tText String: "+textString)
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndText")
|
||||||
break;
|
break;
|
||||||
return thisText
|
return thisText
|
||||||
|
|
||||||
def readNode(self):
|
def readNode(self):
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginNode")
|
||||||
|
|
||||||
##reads in a node type structure = an electrical net
|
##reads in a node type structure = an electrical net
|
||||||
thisNode = GdsNode()
|
thisNode = GdsNode()
|
||||||
while 1:
|
while 1:
|
||||||
|
|
@ -513,10 +539,15 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndNode")
|
||||||
break;
|
break;
|
||||||
return thisNode
|
return thisNode
|
||||||
|
|
||||||
def readBox(self):
|
def readBox(self):
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tBeginBox")
|
||||||
|
|
||||||
##reads in a gds BOX structure
|
##reads in a gds BOX structure
|
||||||
thisBox = GdsBox()
|
thisBox = GdsBox()
|
||||||
while 1:
|
while 1:
|
||||||
|
|
@ -539,9 +570,9 @@ class Gds2reader:
|
||||||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||||
elif(idBits==b'\x16\x02'): #Purpose
|
elif(idBits==b'\x16\x02'): #Purpose TEXTYPE
|
||||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||||
thisBox.purposeLayer=purposeLayer
|
thisBox.purposeLayer=purposeLayer
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||||
elif(idBits==b'\x2D\x00'): #Box
|
elif(idBits==b'\x2D\x00'): #Box
|
||||||
|
|
@ -559,15 +590,18 @@ class Gds2reader:
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||||
elif(idBits==b'\x11\x00'): #End Of Element
|
elif(idBits==b'\x11\x00'): #End Of Element
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("\t\t\tEndBox")
|
||||||
break;
|
break;
|
||||||
return thisBox
|
return thisBox
|
||||||
|
|
||||||
def readNextStructure(self):
|
def readNextStructure(self):
|
||||||
thisStructure = GdsStructure()
|
thisStructure = GdsStructure()
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
idBits = record[0:2]
|
idBits = record[0:2]
|
||||||
|
# Begin structure
|
||||||
if(idBits==b'\x05\x02' and len(record)==26):
|
if(idBits==b'\x05\x02' and len(record)==26):
|
||||||
createYear = struct.unpack(">h",record[2:4])[0]
|
createYear = struct.unpack(">h",record[2:4])[0]
|
||||||
createMonth = struct.unpack(">h",record[4:6])[0]
|
createMonth = struct.unpack(">h",record[4:6])[0]
|
||||||
createDay = struct.unpack(">h",record[6:8])[0]
|
createDay = struct.unpack(">h",record[6:8])[0]
|
||||||
createHour = struct.unpack(">h",record[8:10])[0]
|
createHour = struct.unpack(">h",record[8:10])[0]
|
||||||
|
|
@ -581,6 +615,10 @@ class Gds2reader:
|
||||||
modSecond = struct.unpack(">h",record[24:26])[0]
|
modSecond = struct.unpack(">h",record[24:26])[0]
|
||||||
thisStructure.createDate=(createYear,createMonth,createDay,createHour,createMinute,createSecond)
|
thisStructure.createDate=(createYear,createMonth,createDay,createHour,createMinute,createSecond)
|
||||||
thisStructure.modDate=(modYear,modMonth,modDay,modHour,modMinute,modSecond)
|
thisStructure.modDate=(modYear,modMonth,modDay,modHour,modMinute,modSecond)
|
||||||
|
if(self.debugToTerminal==1):
|
||||||
|
print("Date Created:"+str(createYear)+","+str(createMonth)+","+str(createDay)+\
|
||||||
|
","+str(createHour)+","+str(createMinute)+","+str(createSecond))
|
||||||
|
print("Date Modified:"+str(modYear)+","+str(modMonth)+","+str(modDay)+","+str(modHour)+","+str(modMinute)+","+str(modSecond))
|
||||||
else:
|
else:
|
||||||
#means we have hit the last structure, so return the record
|
#means we have hit the last structure, so return the record
|
||||||
#to whoever called us to do something with it
|
#to whoever called us to do something with it
|
||||||
|
|
@ -590,7 +628,7 @@ class Gds2reader:
|
||||||
idBits = record[0:2]
|
idBits = record[0:2]
|
||||||
if idBits==b'\x07\x00': break; #we've reached the end of the structure
|
if idBits==b'\x07\x00': break; #we've reached the end of the structure
|
||||||
elif(idBits==b'\x06\x06'):
|
elif(idBits==b'\x06\x06'):
|
||||||
structName = self.stripNonASCII(record[2::])
|
structName = self.stripNonASCII(record[2::])
|
||||||
thisStructure.name = structName
|
thisStructure.name = structName
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\tStructure Name: "+structName)
|
print("\tStructure Name: "+structName)
|
||||||
|
|
@ -608,11 +646,11 @@ class Gds2reader:
|
||||||
thisStructure.nodes+=[self.readNode()]
|
thisStructure.nodes+=[self.readNode()]
|
||||||
elif(idBits==b'\x2E\x02'):
|
elif(idBits==b'\x2E\x02'):
|
||||||
thisStructure.boxes+=[self.readBox()]
|
thisStructure.boxes+=[self.readBox()]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\tEnd of Structure.")
|
print("\tEnd of Structure.")
|
||||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def readGds2(self):
|
def readGds2(self):
|
||||||
if(self.readHeader()): #did the header read ok?
|
if(self.readHeader()): #did the header read ok?
|
||||||
record = self.readNextStructure()
|
record = self.readNextStructure()
|
||||||
|
|
@ -629,7 +667,7 @@ class Gds2reader:
|
||||||
print("There was an error reading the structure list.")
|
print("There was an error reading the structure list.")
|
||||||
else:
|
else:
|
||||||
print("There was an error parsing the GDS header. Aborting...")
|
print("There was an error parsing the GDS header. Aborting...")
|
||||||
|
|
||||||
def loadFromFile(self, fileName):
|
def loadFromFile(self, fileName):
|
||||||
self.fileHandle = open(fileName,"rb")
|
self.fileHandle = open(fileName,"rb")
|
||||||
self.readGds2()
|
self.readGds2()
|
||||||
|
|
@ -657,11 +695,11 @@ class Gds2reader:
|
||||||
|
|
||||||
def findStruct_readNextStruct(self,findStructName):
|
def findStruct_readNextStruct(self,findStructName):
|
||||||
self.debugToTerminal=0
|
self.debugToTerminal=0
|
||||||
thisStructure = GdsStructure()
|
thisStructure = GdsStructure()
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
idBits = record[0:2]
|
idBits = record[0:2]
|
||||||
if(idBits==('\x05','\x02') and len(record)==26):
|
if(idBits==('\x05','\x02') and len(record)==26):
|
||||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||||
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
||||||
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
||||||
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
||||||
|
|
@ -686,10 +724,6 @@ class Gds2reader:
|
||||||
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
||||||
elif(idBits==('\x06','\x06')):
|
elif(idBits==('\x06','\x06')):
|
||||||
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
||||||
# print(''.[x for x in structName if ord(x) < 128])
|
|
||||||
# stripped = (c for c in structName if 0 < ord(c) < 127)
|
|
||||||
# structName = "".join(stripped)
|
|
||||||
# print(self.stripNonASCII(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
|
|
||||||
thisStructure.name = structName
|
thisStructure.name = structName
|
||||||
if(findStructName==thisStructure.name):
|
if(findStructName==thisStructure.name):
|
||||||
wantedStruct=1
|
wantedStruct=1
|
||||||
|
|
@ -709,7 +743,7 @@ class Gds2reader:
|
||||||
thisStructure.nodes+=[self.readNode()]
|
thisStructure.nodes+=[self.readNode()]
|
||||||
elif(idBits==('\x2E','\x02')):
|
elif(idBits==('\x2E','\x02')):
|
||||||
thisStructure.boxes+=[self.readBox()]
|
thisStructure.boxes+=[self.readBox()]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\tEnd of Structure.")
|
print("\tEnd of Structure.")
|
||||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||||
if(wantedStruct == 0):
|
if(wantedStruct == 0):
|
||||||
|
|
@ -737,11 +771,11 @@ class Gds2reader:
|
||||||
|
|
||||||
def findLabel_readNextStruct(self,findLabelName):
|
def findLabel_readNextStruct(self,findLabelName):
|
||||||
self.debugToTerminal=0
|
self.debugToTerminal=0
|
||||||
thisStructure = GdsStructure()
|
thisStructure = GdsStructure()
|
||||||
record = self.readNextRecord()
|
record = self.readNextRecord()
|
||||||
idBits = record[0:2]
|
idBits = record[0:2]
|
||||||
if(idBits==('\x05','\x02') and len(record)==26):
|
if(idBits==('\x05','\x02') and len(record)==26):
|
||||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||||
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
||||||
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
||||||
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
||||||
|
|
@ -767,10 +801,6 @@ class Gds2reader:
|
||||||
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
||||||
elif(idBits==('\x06','\x06')):
|
elif(idBits==('\x06','\x06')):
|
||||||
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
||||||
# print(''.[x for x in structName if ord(x) < 128])
|
|
||||||
# stripped = (c for c in structName if 0 < ord(c) < 127)
|
|
||||||
# structName = "".join(stripped)
|
|
||||||
# print(self.stripNonASCIIx(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
|
|
||||||
thisStructure.name = structName
|
thisStructure.name = structName
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\tStructure Name: "+structName)
|
print("\tStructure Name: "+structName)
|
||||||
|
|
@ -795,7 +825,7 @@ class Gds2reader:
|
||||||
thisStructure.nodes+=[self.readNode()]
|
thisStructure.nodes+=[self.readNode()]
|
||||||
elif(idBits==('\x2E','\x02')):
|
elif(idBits==('\x2E','\x02')):
|
||||||
thisStructure.boxes+=[self.readBox()]
|
thisStructure.boxes+=[self.readBox()]
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("\tEnd of Structure.")
|
print("\tEnd of Structure.")
|
||||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||||
if(wantedLabel == 0):
|
if(wantedLabel == 0):
|
||||||
|
|
@ -803,4 +833,3 @@ class Gds2reader:
|
||||||
else:
|
else:
|
||||||
#print("\tDone with collectting bound. Return")
|
#print("\tDone with collectting bound. Return")
|
||||||
return [0,wantedtexts]
|
return [0,wantedtexts]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,37 +5,37 @@ from .gdsPrimitives import *
|
||||||
class Gds2writer:
|
class Gds2writer:
|
||||||
"""Class to take a populated layout class and write it to a file in GDSII format"""
|
"""Class to take a populated layout class and write it to a file in GDSII format"""
|
||||||
## Based on info from http://www.rulabinsky.com/cavd/text/chapc.html
|
## Based on info from http://www.rulabinsky.com/cavd/text/chapc.html
|
||||||
|
|
||||||
def __init__(self,layoutObject):
|
def __init__(self,layoutObject):
|
||||||
self.fileHandle = 0
|
self.fileHandle = 0
|
||||||
self.layoutObject = layoutObject
|
self.layoutObject = layoutObject
|
||||||
self.debugToTerminal=0 #do we dump debug data to the screen
|
self.debugToTerminal=0 #do we dump debug data to the screen
|
||||||
|
|
||||||
def print64AsBinary(self,number):
|
def print64AsBinary(self,number):
|
||||||
#debugging method for binary inspection
|
#debugging method for binary inspection
|
||||||
for index in range(0,64):
|
for index in range(0,64):
|
||||||
print((number>>(63-index))&0x1,eol='')
|
print((number>>(63-index))&0x1,eol='')
|
||||||
print("\n")
|
print("\n")
|
||||||
|
|
||||||
def ieeeDoubleFromIbmData(self,ibmData):
|
def ieeeDoubleFromIbmData(self,ibmData):
|
||||||
#the GDS double is in IBM 370 format like this:
|
#the GDS double is in IBM 370 format like this:
|
||||||
#(1)sign (7)exponent (56)mantissa
|
#(1)sign (7)exponent (56)mantissa
|
||||||
#exponent is excess 64, mantissa has no implied 1
|
#exponent is excess 64, mantissa has no implied 1
|
||||||
#a normal IEEE double is like this:
|
#a normal IEEE double is like this:
|
||||||
#(1)sign (11)exponent (52)mantissa
|
#(1)sign (11)exponent (52)mantissa
|
||||||
data = struct.unpack('>q',ibmData)[0]
|
data = struct.unpack('>q',ibmData)[0]
|
||||||
sign = (data >> 63)&0x01
|
sign = (data >> 63)&0x01
|
||||||
exponent = (data >> 56) & 0x7f
|
exponent = (data >> 56) & 0x7f
|
||||||
mantissa = data<<8 #chop off sign and exponent
|
mantissa = data<<8 #chop off sign and exponent
|
||||||
|
|
||||||
if mantissa == 0:
|
if mantissa == 0:
|
||||||
newFloat = 0.0
|
newFloat = 0.0
|
||||||
else:
|
else:
|
||||||
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
||||||
#re normalize
|
#re normalize
|
||||||
while mantissa & 0x8000000000000000 == 0:
|
while mantissa & 0x8000000000000000 == 0:
|
||||||
mantissa<<=1
|
mantissa<<=1
|
||||||
exponent-=1
|
exponent-=1
|
||||||
mantissa<<=1 #remove the assumed high bit
|
mantissa<<=1 #remove the assumed high bit
|
||||||
exponent-=1
|
exponent-=1
|
||||||
#check for underflow error -- should handle these properly!
|
#check for underflow error -- should handle these properly!
|
||||||
|
|
@ -49,12 +49,12 @@ class Gds2writer:
|
||||||
#convert back to double
|
#convert back to double
|
||||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||||
return newFloat
|
return newFloat
|
||||||
|
|
||||||
def ibmDataFromIeeeDouble(self,ieeeDouble):
|
def ibmDataFromIeeeDouble(self,ieeeDouble):
|
||||||
asciiDouble = struct.pack('>d',ieeeDouble)
|
asciiDouble = struct.pack('>d',ieeeDouble)
|
||||||
data = struct.unpack('>q',asciiDouble)[0]
|
data = struct.unpack('>q',asciiDouble)[0]
|
||||||
sign = (data >> 63) & 0x01
|
sign = (data >> 63) & 0x01
|
||||||
exponent = ((data >> 52) & 0x7ff)-1023
|
exponent = ((data >> 52) & 0x7ff)-1023
|
||||||
mantissa = data << 12 #chop off sign and exponent
|
mantissa = data << 12 #chop off sign and exponent
|
||||||
if(ieeeDouble == 0):
|
if(ieeeDouble == 0):
|
||||||
mantissa = 0
|
mantissa = 0
|
||||||
|
|
@ -70,14 +70,14 @@ class Gds2writer:
|
||||||
for index in range (0,-exponent&3):
|
for index in range (0,-exponent&3):
|
||||||
mantissa >>= 1
|
mantissa >>= 1
|
||||||
mantissa = mantissa & 0x7fffffffffffffff
|
mantissa = mantissa & 0x7fffffffffffffff
|
||||||
|
|
||||||
exponent = (exponent+3) >> 2
|
exponent = (exponent+3) >> 2
|
||||||
exponent+=64
|
exponent+=64
|
||||||
|
|
||||||
newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff)
|
newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff)
|
||||||
asciiDouble = struct.pack('>q',newFloat)
|
asciiDouble = struct.pack('>q',newFloat)
|
||||||
return asciiDouble
|
return asciiDouble
|
||||||
|
|
||||||
def ieeeFloatCheck(self,aFloat):
|
def ieeeFloatCheck(self,aFloat):
|
||||||
#debugging method for float construction
|
#debugging method for float construction
|
||||||
asciiDouble = struct.pack('>d',aFloat)
|
asciiDouble = struct.pack('>d',aFloat)
|
||||||
|
|
@ -90,7 +90,7 @@ class Gds2writer:
|
||||||
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
||||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||||
print("Check:"+str(newFloat))
|
print("Check:"+str(newFloat))
|
||||||
|
|
||||||
def writeRecord(self,record):
|
def writeRecord(self,record):
|
||||||
recordLength = len(record)+2 #make sure to include this in the length
|
recordLength = len(record)+2 #make sure to include this in the length
|
||||||
recordLengthAscii=struct.pack(">h",recordLength)
|
recordLengthAscii=struct.pack(">h",recordLength)
|
||||||
|
|
@ -127,7 +127,7 @@ class Gds2writer:
|
||||||
libraryName = self.layoutObject.info["libraryName"].encode() + "\0"
|
libraryName = self.layoutObject.info["libraryName"].encode() + "\0"
|
||||||
else:
|
else:
|
||||||
libraryName = self.layoutObject.info["libraryName"].encode()
|
libraryName = self.layoutObject.info["libraryName"].encode()
|
||||||
self.writeRecord(idBits+libraryName)
|
self.writeRecord(idBits+libraryName)
|
||||||
## reference libraries
|
## reference libraries
|
||||||
if("referenceLibraries" in self.layoutObject.info):
|
if("referenceLibraries" in self.layoutObject.info):
|
||||||
idBits=b'\x1F\x06'
|
idBits=b'\x1F\x06'
|
||||||
|
|
@ -158,11 +158,11 @@ class Gds2writer:
|
||||||
mask = self.layoutObject.info["mask"]
|
mask = self.layoutObject.info["mask"]
|
||||||
self.writeRecord(idBits+mask)
|
self.writeRecord(idBits+mask)
|
||||||
if("units" in self.layoutObject.info):
|
if("units" in self.layoutObject.info):
|
||||||
idBits=b'\x03\x05'
|
idBits=b'\x03\x05'
|
||||||
userUnits=self.ibmDataFromIeeeDouble(self.layoutObject.info["units"][0])
|
userUnits=self.ibmDataFromIeeeDouble(self.layoutObject.info["units"][0])
|
||||||
dbUnits=self.ibmDataFromIeeeDouble((self.layoutObject.info["units"][0]*1e-6/self.layoutObject.info["units"][1])*self.layoutObject.info["units"][1])
|
dbUnits=self.ibmDataFromIeeeDouble((self.layoutObject.info["units"][0]*1e-6/self.layoutObject.info["units"][1])*self.layoutObject.info["units"][1])
|
||||||
|
|
||||||
#User Units are hardcoded, since the floating point implementation of gdsMill is not adequate,
|
#User Units are hardcoded, since the floating point implementation of gdsMill is not adequate,
|
||||||
#resulting in a different value being written in output stream. Hardcoded to sram compiler's outputed gds units.
|
#resulting in a different value being written in output stream. Hardcoded to sram compiler's outputed gds units.
|
||||||
#db="39225c17d04dad2a"
|
#db="39225c17d04dad2a"
|
||||||
#uu="3e20c49ba5e353f8"
|
#uu="3e20c49ba5e353f8"
|
||||||
|
|
@ -172,42 +172,40 @@ class Gds2writer:
|
||||||
|
|
||||||
#dbUnits="39225c17d04dad2a".decode("hex")
|
#dbUnits="39225c17d04dad2a".decode("hex")
|
||||||
#db=39225c17d04dad2a
|
#db=39225c17d04dad2a
|
||||||
|
|
||||||
|
|
||||||
self.writeRecord(idBits+userUnits+dbUnits)
|
self.writeRecord(idBits+userUnits+dbUnits)
|
||||||
if(self.debugToTerminal==1):
|
if(self.debugToTerminal==1):
|
||||||
print("writer: userUnits %s"%(userUnits.encode("hex")))
|
print("writer: userUnits %s"%(userUnits.encode("hex")))
|
||||||
print("writer: dbUnits %s"%(dbUnits.encode("hex")))
|
print("writer: dbUnits %s"%(dbUnits.encode("hex")))
|
||||||
#self.ieeeFloatCheck(1.3e-6)
|
#self.ieeeFloatCheck(1.3e-6)
|
||||||
|
|
||||||
print("End of GDSII Header Written")
|
print("End of GDSII Header Written")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def writeBoundary(self,thisBoundary):
|
def writeBoundary(self,thisBoundary):
|
||||||
idBits=b'\x08\x00' #record Type
|
idBits=b'\x08\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
if(thisBoundary.elementFlags!=""):
|
if(thisBoundary.elementFlags!=""):
|
||||||
idBits=b'\x26\x01' #ELFLAGS
|
idBits=b'\x26\x01' # ELFLAGS
|
||||||
elementFlags = struct.pack(">h",thisBoundary.elementFlags)
|
elementFlags = struct.pack(">h",thisBoundary.elementFlags)
|
||||||
self.writeRecord(idBits+elementFlags)
|
self.writeRecord(idBits+elementFlags)
|
||||||
if(thisBoundary.plex!=""):
|
if(thisBoundary.plex!=""):
|
||||||
idBits=b'\x2F\x03' #PLEX
|
idBits=b'\x2F\x03' # PLEX
|
||||||
plex = struct.pack(">i",thisBoundary.plex)
|
plex = struct.pack(">i",thisBoundary.plex)
|
||||||
self.writeRecord(idBits+plex)
|
self.writeRecord(idBits+plex)
|
||||||
if(thisBoundary.drawingLayer!=""):
|
if(thisBoundary.drawingLayer!=""):
|
||||||
idBits=b'\x0D\x02' #drawig layer
|
idBits=b'\x0D\x02' # drawing layer
|
||||||
drawingLayer = struct.pack(">h",thisBoundary.drawingLayer)
|
drawingLayer = struct.pack(">h",thisBoundary.drawingLayer)
|
||||||
self.writeRecord(idBits+drawingLayer)
|
self.writeRecord(idBits+drawingLayer)
|
||||||
if(thisBoundary.purposeLayer):
|
if(thisBoundary.purposeLayer!=""):
|
||||||
idBits=b'\x16\x02' #purpose layer
|
idBits=b'\x0E\x02' # DataType
|
||||||
purposeLayer = struct.pack(">h",thisBoundary.purposeLayer)
|
if type(thisBoundary.purposeLayer)!=int:
|
||||||
self.writeRecord(idBits+purposeLayer)
|
import pdb; pdb.set_trace()
|
||||||
if(thisBoundary.dataType!=""):
|
dataType = struct.pack(">h",thisBoundary.purposeLayer)
|
||||||
idBits=b'\x0E\x02'#DataType
|
|
||||||
dataType = struct.pack(">h",thisBoundary.dataType)
|
|
||||||
self.writeRecord(idBits+dataType)
|
self.writeRecord(idBits+dataType)
|
||||||
if(thisBoundary.coordinates!=""):
|
if(thisBoundary.coordinates!=""):
|
||||||
idBits=b'\x10\x03' #XY Data Points
|
idBits=b'\x10\x03' # XY Data Points
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
for coordinate in thisBoundary.coordinates:
|
for coordinate in thisBoundary.coordinates:
|
||||||
x=struct.pack(">i",int(coordinate[0]))
|
x=struct.pack(">i",int(coordinate[0]))
|
||||||
|
|
@ -218,7 +216,7 @@ class Gds2writer:
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writePath(self,thisPath): #writes out a path structure
|
def writePath(self,thisPath): #writes out a path structure
|
||||||
idBits=b'\x09\x00' #record Type
|
idBits=b'\x09\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -238,6 +236,10 @@ class Gds2writer:
|
||||||
idBits=b'\x16\x02' #purpose layer
|
idBits=b'\x16\x02' #purpose layer
|
||||||
purposeLayer = struct.pack(">h",thisPath.purposeLayer)
|
purposeLayer = struct.pack(">h",thisPath.purposeLayer)
|
||||||
self.writeRecord(idBits+purposeLayer)
|
self.writeRecord(idBits+purposeLayer)
|
||||||
|
if(thisPath.dataType is not None):
|
||||||
|
idBits=b'\x0E\x02' #Data type
|
||||||
|
dataType = struct.pack(">h",thisPath.dataType)
|
||||||
|
self.writeRecord(idBits+dataType)
|
||||||
if(thisPath.pathType):
|
if(thisPath.pathType):
|
||||||
idBits=b'\x21\x02' #Path type
|
idBits=b'\x21\x02' #Path type
|
||||||
pathType = struct.pack(">h",thisPath.pathType)
|
pathType = struct.pack(">h",thisPath.pathType)
|
||||||
|
|
@ -258,7 +260,7 @@ class Gds2writer:
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeSref(self,thisSref): #reads in a reference to another structure
|
def writeSref(self,thisSref): #reads in a reference to another structure
|
||||||
idBits=b'\x0A\x00' #record Type
|
idBits=b'\x0A\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -294,7 +296,7 @@ class Gds2writer:
|
||||||
magFactor=self.ibmDataFromIeeeDouble(thisSref.magFactor)
|
magFactor=self.ibmDataFromIeeeDouble(thisSref.magFactor)
|
||||||
self.writeRecord(idBits+magFactor)
|
self.writeRecord(idBits+magFactor)
|
||||||
if(thisSref.rotateAngle!=""):
|
if(thisSref.rotateAngle!=""):
|
||||||
idBits=b'\x1C\x05'
|
idBits=b'\x1C\x05'
|
||||||
rotateAngle=self.ibmDataFromIeeeDouble(thisSref.rotateAngle)
|
rotateAngle=self.ibmDataFromIeeeDouble(thisSref.rotateAngle)
|
||||||
self.writeRecord(idBits+rotateAngle)
|
self.writeRecord(idBits+rotateAngle)
|
||||||
if(thisSref.coordinates!=""):
|
if(thisSref.coordinates!=""):
|
||||||
|
|
@ -310,7 +312,7 @@ class Gds2writer:
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeAref(self,thisAref): #an array of references
|
def writeAref(self,thisAref): #an array of references
|
||||||
idBits=b'\x0B\x00' #record Type
|
idBits=b'\x0B\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -346,7 +348,7 @@ class Gds2writer:
|
||||||
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)
|
||||||
if(thisAref.coordinates):
|
if(thisAref.coordinates):
|
||||||
|
|
@ -361,7 +363,7 @@ class Gds2writer:
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeText(self,thisText):
|
def writeText(self,thisText):
|
||||||
idBits=b'\x0C\x00' #record Type
|
idBits=b'\x0C\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -377,8 +379,7 @@ class Gds2writer:
|
||||||
idBits=b'\x0D\x02' #drawing layer
|
idBits=b'\x0D\x02' #drawing layer
|
||||||
drawingLayer = struct.pack(">h",thisText.drawingLayer)
|
drawingLayer = struct.pack(">h",thisText.drawingLayer)
|
||||||
self.writeRecord(idBits+drawingLayer)
|
self.writeRecord(idBits+drawingLayer)
|
||||||
#if(thisText.purposeLayer):
|
idBits=b'\x16\x02' #purpose layer TEXTTYPE
|
||||||
idBits=b'\x16\x02' #purpose layer
|
|
||||||
purposeLayer = struct.pack(">h",thisText.purposeLayer)
|
purposeLayer = struct.pack(">h",thisText.purposeLayer)
|
||||||
self.writeRecord(idBits+purposeLayer)
|
self.writeRecord(idBits+purposeLayer)
|
||||||
if(thisText.transFlags != ""):
|
if(thisText.transFlags != ""):
|
||||||
|
|
@ -398,7 +399,7 @@ class Gds2writer:
|
||||||
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)
|
||||||
if(thisText.pathType !=""):
|
if(thisText.pathType !=""):
|
||||||
|
|
@ -410,12 +411,12 @@ class Gds2writer:
|
||||||
pathWidth = struct.pack(">i",thisText.pathWidth)
|
pathWidth = struct.pack(">i",thisText.pathWidth)
|
||||||
self.writeRecord(idBits+pathWidth)
|
self.writeRecord(idBits+pathWidth)
|
||||||
if(thisText.presentationFlags!=""):
|
if(thisText.presentationFlags!=""):
|
||||||
idBits=b'\x1A\x01'
|
idBits=b'\x1A\x01'
|
||||||
font = thisText.presentationFlags[0]<<4
|
font = thisText.presentationFlags[0]<<4
|
||||||
verticalFlags = int(thisText.presentationFlags[1])<<2
|
verticalFlags = int(thisText.presentationFlags[1])<<2
|
||||||
horizontalFlags = int(thisText.presentationFlags[2])
|
horizontalFlags = int(thisText.presentationFlags[2])
|
||||||
presentationFlags = struct.pack(">H",font|verticalFlags|horizontalFlags)
|
presentationFlags = struct.pack(">H",font|verticalFlags|horizontalFlags)
|
||||||
self.writeRecord(idBits+transFlags)
|
self.writeRecord(idBits+transFlags)
|
||||||
if(thisText.coordinates!=""):
|
if(thisText.coordinates!=""):
|
||||||
idBits=b'\x10\x03' #XY Data Points
|
idBits=b'\x10\x03' #XY Data Points
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
|
|
@ -429,11 +430,11 @@ class Gds2writer:
|
||||||
idBits=b'\x19\x06'
|
idBits=b'\x19\x06'
|
||||||
textString = thisText.textString
|
textString = thisText.textString
|
||||||
self.writeRecord(idBits+textString.encode())
|
self.writeRecord(idBits+textString.encode())
|
||||||
|
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeNode(self,thisNode):
|
def writeNode(self,thisNode):
|
||||||
idBits=b'\x15\x00' #record Type
|
idBits=b'\x15\x00' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -448,11 +449,11 @@ class Gds2writer:
|
||||||
if(thisNode.drawingLayer!=""):
|
if(thisNode.drawingLayer!=""):
|
||||||
idBits=b'\x0D\x02' #drawig layer
|
idBits=b'\x0D\x02' #drawig layer
|
||||||
drawingLayer = struct.pack(">h",thisNode.drawingLayer)
|
drawingLayer = struct.pack(">h",thisNode.drawingLayer)
|
||||||
self.writeRecord(idBits+drawingLayer)
|
self.writeRecord(idBits+drawingLayer)
|
||||||
if(thisNode.nodeType!=""):
|
if(thisNode.nodeType!=""):
|
||||||
idBits=b'\x2A\x02'
|
idBits=b'\x2A\x02'
|
||||||
nodeType = struct.pack(">h",thisNode.nodeType)
|
nodeType = struct.pack(">h",thisNode.nodeType)
|
||||||
self.writeRecord(idBits+nodeType)
|
self.writeRecord(idBits+nodeType)
|
||||||
if(thisText.coordinates!=""):
|
if(thisText.coordinates!=""):
|
||||||
idBits=b'\x10\x03' #XY Data Points
|
idBits=b'\x10\x03' #XY Data Points
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
|
|
@ -462,11 +463,11 @@ class Gds2writer:
|
||||||
coordinateRecord+=x
|
coordinateRecord+=x
|
||||||
coordinateRecord+=y
|
coordinateRecord+=y
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeBox(self,thisBox):
|
def writeBox(self,thisBox):
|
||||||
idBits=b'\x2E\x02' #record Type
|
idBits=b'\x2E\x02' #record Type
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
@ -489,7 +490,7 @@ class Gds2writer:
|
||||||
if(thisBox.boxValue!=""):
|
if(thisBox.boxValue!=""):
|
||||||
idBits=b'\x2D\x00'
|
idBits=b'\x2D\x00'
|
||||||
boxValue = struct.pack(">h",thisBox.boxValue)
|
boxValue = struct.pack(">h",thisBox.boxValue)
|
||||||
self.writeRecord(idBits+boxValue)
|
self.writeRecord(idBits+boxValue)
|
||||||
if(thisBox.coordinates!=""):
|
if(thisBox.coordinates!=""):
|
||||||
idBits=b'\x10\x03' #XY Data Points
|
idBits=b'\x10\x03' #XY Data Points
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
|
|
@ -499,11 +500,11 @@ class Gds2writer:
|
||||||
coordinateRecord+=x
|
coordinateRecord+=x
|
||||||
coordinateRecord+=y
|
coordinateRecord+=y
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
idBits=b'\x11\x00' #End Of Element
|
idBits=b'\x11\x00' #End Of Element
|
||||||
coordinateRecord = idBits
|
coordinateRecord = idBits
|
||||||
self.writeRecord(coordinateRecord)
|
self.writeRecord(coordinateRecord)
|
||||||
|
|
||||||
def writeNextStructure(self,structureName):
|
def writeNextStructure(self,structureName):
|
||||||
#first put in the structure head
|
#first put in the structure head
|
||||||
thisStructure = self.layoutObject.structures[structureName]
|
thisStructure = self.layoutObject.structures[structureName]
|
||||||
|
|
@ -530,7 +531,7 @@ class Gds2writer:
|
||||||
structureName = structureName + '\x00'
|
structureName = structureName + '\x00'
|
||||||
self.writeRecord(idBits+structureName.encode())
|
self.writeRecord(idBits+structureName.encode())
|
||||||
#now go through all the structure elements and write them in
|
#now go through all the structure elements and write them in
|
||||||
|
|
||||||
for boundary in thisStructure.boundaries:
|
for boundary in thisStructure.boundaries:
|
||||||
self.writeBoundary(boundary)
|
self.writeBoundary(boundary)
|
||||||
for path in thisStructure.paths:
|
for path in thisStructure.paths:
|
||||||
|
|
@ -548,7 +549,7 @@ class Gds2writer:
|
||||||
#put in the structure tail
|
#put in the structure tail
|
||||||
idBits=b'\x07\x00'
|
idBits=b'\x07\x00'
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
||||||
def writeGds2(self):
|
def writeGds2(self):
|
||||||
self.writeHeader(); #first, put the header in
|
self.writeHeader(); #first, put the header in
|
||||||
#go through each structure in the layout and write it to the file
|
#go through each structure in the layout and write it to the file
|
||||||
|
|
@ -557,7 +558,7 @@ class Gds2writer:
|
||||||
#at the end, put in the END LIB record
|
#at the end, put in the END LIB record
|
||||||
idBits=b'\x04\x00'
|
idBits=b'\x04\x00'
|
||||||
self.writeRecord(idBits)
|
self.writeRecord(idBits)
|
||||||
|
|
||||||
def writeToFile(self,fileName):
|
def writeToFile(self,fileName):
|
||||||
self.fileHandle = open(fileName,"wb")
|
self.fileHandle = open(fileName,"wb")
|
||||||
self.writeGds2()
|
self.writeGds2()
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class GdsStructure:
|
||||||
#these are the primitives defined in GDS2, and we will maintain lists of them all
|
#these are the primitives defined in GDS2, and we will maintain lists of them all
|
||||||
self.boundaries=[]
|
self.boundaries=[]
|
||||||
self.paths=[]
|
self.paths=[]
|
||||||
self.srefs=[]
|
self.srefs=[]
|
||||||
self.arefs=[]
|
self.arefs=[]
|
||||||
self.texts=[]
|
self.texts=[]
|
||||||
self.nodes=[]
|
self.nodes=[]
|
||||||
|
|
@ -21,21 +21,21 @@ class GdsBoundary:
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer=0
|
||||||
self.dataType=""
|
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
||||||
class GdsPath:
|
class GdsPath:
|
||||||
"""Class represent a GDS Path Object"""
|
"""Class represent a GDS Path Object"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer=0
|
||||||
self.pathType=""
|
self.pathType=""
|
||||||
|
self.dataType=None
|
||||||
self.pathWidth=""
|
self.pathWidth=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
||||||
def equivalentBoundaryCoordinates(self):
|
def equivalentBoundaryCoordinates(self):
|
||||||
"""Convert the path to a set of boundary coordinates that define it"""
|
"""Convert the path to a set of boundary coordinates that define it"""
|
||||||
halfWidth = (self.pathWidth/2)
|
halfWidth = (self.pathWidth/2)
|
||||||
|
|
@ -62,7 +62,7 @@ class GdsPath:
|
||||||
nextX = None;
|
nextX = None;
|
||||||
nextY = None;
|
nextY = None;
|
||||||
if lastX==None: #start of the path
|
if lastX==None: #start of the path
|
||||||
if nextX>x:#moving right
|
if nextX>x:#moving right
|
||||||
boundaryEquivalent+=[(x,y+halfWidth)]
|
boundaryEquivalent+=[(x,y+halfWidth)]
|
||||||
if nextX<x:#moving left
|
if nextX<x:#moving left
|
||||||
boundaryEquivalent+=[(x,y-halfWidth)]
|
boundaryEquivalent+=[(x,y-halfWidth)]
|
||||||
|
|
@ -96,9 +96,9 @@ class GdsPath:
|
||||||
boundaryEquivalent+=[(x-halfWidth,y+halfWidth)]
|
boundaryEquivalent+=[(x-halfWidth,y+halfWidth)]
|
||||||
if(y < lastY and x > nextX):
|
if(y < lastY and x > nextX):
|
||||||
boundaryEquivalent+=[(x+halfWidth,y-halfWidth)]
|
boundaryEquivalent+=[(x+halfWidth,y-halfWidth)]
|
||||||
|
|
||||||
if nextX == None: #end of path, put in the last 2 points
|
if nextX == None: #end of path, put in the last 2 points
|
||||||
if lastX<x:#moving right
|
if lastX<x:#moving right
|
||||||
boundaryEquivalent+=[(x,y+halfWidth)]
|
boundaryEquivalent+=[(x,y+halfWidth)]
|
||||||
if lastX>x:#moving left
|
if lastX>x:#moving left
|
||||||
boundaryEquivalent+=[(x,y-halfWidth)]
|
boundaryEquivalent+=[(x,y-halfWidth)]
|
||||||
|
|
@ -140,7 +140,7 @@ class GdsText:
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer=0
|
||||||
self.transFlags=[0,0,0]
|
self.transFlags=[0,0,0]
|
||||||
self.magFactor=""
|
self.magFactor=""
|
||||||
self.rotateAngle=""
|
self.rotateAngle=""
|
||||||
|
|
@ -149,7 +149,7 @@ class GdsText:
|
||||||
self.presentationFlags=""
|
self.presentationFlags=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
self.textString = ""
|
self.textString = ""
|
||||||
|
|
||||||
class GdsNode:
|
class GdsNode:
|
||||||
"""Class represent a GDS Node Object"""
|
"""Class represent a GDS Node Object"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -158,13 +158,13 @@ class GdsNode:
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.nodeType=""
|
self.nodeType=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
||||||
class GdsBox:
|
class GdsBox:
|
||||||
"""Class represent a GDS Box Object"""
|
"""Class represent a GDS Box Object"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.elementFlags=""
|
self.elementFlags=""
|
||||||
self.plex=""
|
self.plex=""
|
||||||
self.drawingLayer=""
|
self.drawingLayer=""
|
||||||
self.purposeLayer = None
|
self.purposeLayer=0
|
||||||
self.boxValue=""
|
self.boxValue=""
|
||||||
self.coordinates=""
|
self.coordinates=""
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ from .gdsPrimitives import *
|
||||||
from datetime import *
|
from datetime import *
|
||||||
#from mpmath import matrix
|
#from mpmath import matrix
|
||||||
#from numpy import matrix
|
#from numpy import matrix
|
||||||
from vector import vector
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
#import gdsPrimitives
|
#import gdsPrimitives
|
||||||
import debug
|
import debug
|
||||||
|
|
@ -19,7 +18,8 @@ class VlsiLayout:
|
||||||
self.layerNumbersInUse = []
|
self.layerNumbersInUse = []
|
||||||
self.debug = False
|
self.debug = False
|
||||||
if name:
|
if name:
|
||||||
self.rootStructureName=name
|
#take the root structure and copy it to a new structure with the new name
|
||||||
|
self.rootStructureName=self.padText(name)
|
||||||
#create the ROOT structure
|
#create the ROOT structure
|
||||||
self.structures[self.rootStructureName] = GdsStructure()
|
self.structures[self.rootStructureName] = GdsStructure()
|
||||||
self.structures[self.rootStructureName].name = name
|
self.structures[self.rootStructureName].name = name
|
||||||
|
|
@ -35,7 +35,7 @@ class VlsiLayout:
|
||||||
modDate.hour,
|
modDate.hour,
|
||||||
modDate.minute,
|
modDate.minute,
|
||||||
modDate.second)
|
modDate.second)
|
||||||
|
|
||||||
self.info = dict() #information gathered from the GDSII header
|
self.info = dict() #information gathered from the GDSII header
|
||||||
self.info['units']=self.units
|
self.info['units']=self.units
|
||||||
self.info['dates']=(modDate.year,
|
self.info['dates']=(modDate.year,
|
||||||
|
|
@ -52,12 +52,12 @@ class VlsiLayout:
|
||||||
modDate.second)
|
modDate.second)
|
||||||
self.info['libraryName']=libraryName
|
self.info['libraryName']=libraryName
|
||||||
self.info['gdsVersion']=gdsVersion
|
self.info['gdsVersion']=gdsVersion
|
||||||
|
|
||||||
self.xyTree = [] #This will contain a list of all structure names
|
self.xyTree = [] #This will contain a list of all structure names
|
||||||
#expanded to include srefs / arefs separately.
|
#expanded to include srefs / arefs separately.
|
||||||
#each structure will have an X,Y,offset, and rotate associated
|
#each structure will have an X,Y,offset, and rotate associated
|
||||||
#with it. Populate via traverseTheHierarchy method.
|
#with it. Populate via traverseTheHierarchy method.
|
||||||
|
|
||||||
#temp variables used in delegate functions
|
#temp variables used in delegate functions
|
||||||
self.tempCoordinates=None
|
self.tempCoordinates=None
|
||||||
self.tempPassFail = True
|
self.tempPassFail = True
|
||||||
|
|
@ -73,22 +73,18 @@ class VlsiLayout:
|
||||||
if(rotateAngle):
|
if(rotateAngle):
|
||||||
angle = math.radians(float(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
|
# 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.extend((newX,newY))
|
coordinatesRotate.extend((newX,newY))
|
||||||
return coordinatesRotate
|
return coordinatesRotate
|
||||||
|
|
||||||
def rename(self,newName):
|
def rename(self,newName):
|
||||||
#make sure the newName is a multiple of 2 characters
|
|
||||||
if(len(newName)%2 == 1):
|
|
||||||
#pad with a zero
|
|
||||||
newName = newName + '\x00'
|
|
||||||
#take the root structure and copy it to a new structure with the new name
|
#take the root structure and copy it to a new structure with the new name
|
||||||
self.structures[newName] = self.structures[self.rootStructureName]
|
self.structures[newName] = self.structures[self.rootStructureName]
|
||||||
self.structures[newName].name = newName
|
self.structures[newName].name = self.padText(newName)
|
||||||
#and delete the old root
|
#and delete the old root
|
||||||
del self.structures[self.rootStructureName]
|
del self.structures[self.rootStructureName]
|
||||||
self.rootStructureName = newName
|
self.rootStructureName = newName
|
||||||
|
|
@ -133,8 +129,8 @@ class VlsiLayout:
|
||||||
modDate.hour,
|
modDate.hour,
|
||||||
modDate.minute,
|
modDate.minute,
|
||||||
modDate.second)
|
modDate.second)
|
||||||
|
|
||||||
|
|
||||||
#repopulate the 2d map so drawing occurs correctly
|
#repopulate the 2d map so drawing occurs correctly
|
||||||
self.prepareForWrite()
|
self.prepareForWrite()
|
||||||
|
|
||||||
|
|
@ -159,14 +155,15 @@ class VlsiLayout:
|
||||||
debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames)))
|
debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames)))
|
||||||
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 == ""):
|
||||||
angle = 0
|
angle = 0
|
||||||
else:
|
else:
|
||||||
|
|
@ -193,40 +190,44 @@ class VlsiLayout:
|
||||||
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
|
||||||
#********might have to set the recursion level deeper for big layouts!
|
#********might have to set the recursion level deeper for big layouts!
|
||||||
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
|
try:
|
||||||
#if so, go through each and call this function again
|
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
|
||||||
#if not, return back to the caller (caller can be this function)
|
#if so, go through each and call this function again
|
||||||
for sref in self.structures[startingStructureName].srefs:
|
#if not, return back to the caller (caller can be this function)
|
||||||
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
for sref in self.structures[startingStructureName].srefs:
|
||||||
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
||||||
delegateFunction = delegateFunction,
|
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
||||||
transformPath = transformPath,
|
delegateFunction = delegateFunction,
|
||||||
rotateAngle = sref.rotateAngle,
|
transformPath = transformPath,
|
||||||
transFlags = sref.transFlags,
|
rotateAngle = sref.rotateAngle,
|
||||||
coordinates = sref.coordinates)
|
transFlags = sref.transFlags,
|
||||||
|
coordinates = sref.coordinates)
|
||||||
|
except KeyError:
|
||||||
|
debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1)
|
||||||
|
|
||||||
#MUST HANDLE AREFs HERE AS WELL
|
#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]
|
||||||
return
|
return
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.deduceHierarchy()
|
self.deduceHierarchy()
|
||||||
#self.traverseTheHierarchy()
|
# self.traverseTheHierarchy()
|
||||||
self.populateCoordinateMap()
|
self.populateCoordinateMap()
|
||||||
|
|
||||||
for layerNumber in self.layerNumbersInUse:
|
for layerNumber in self.layerNumbersInUse:
|
||||||
self.processLabelPins(layerNumber)
|
self.processLabelPins((layerNumber, None))
|
||||||
|
|
||||||
|
|
||||||
def populateCoordinateMap(self):
|
def populateCoordinateMap(self):
|
||||||
def addToXyTree(startingStructureName = None,transformPath = None):
|
def addToXyTree(startingStructureName = None,transformPath = None):
|
||||||
uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors
|
uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors
|
||||||
vVector = np.array([[0.0],[1.0],[0.0]])
|
vVector = np.array([[0.0],[1.0],[0.0]])
|
||||||
origin = np.array([[0.0],[0.0],[1.0]]) #and an origin (Z component is 1.0 to indicate position instead of vector)
|
origin = np.array([[0.0],[0.0],[1.0]]) #and an origin (Z component is 1.0 to indicate position instead of vector)
|
||||||
#make a copy of all the transforms and reverse it
|
#make a copy of all the transforms and reverse it
|
||||||
reverseTransformPath = transformPath[:]
|
reverseTransformPath = transformPath[:]
|
||||||
if len(reverseTransformPath) > 1:
|
if len(reverseTransformPath) > 1:
|
||||||
reverseTransformPath.reverse()
|
reverseTransformPath.reverse()
|
||||||
#now go through each transform and apply them to our basis and origin in succession
|
#now go through each transform and apply them to our basis and origin in succession
|
||||||
for transform in reverseTransformPath:
|
for transform in reverseTransformPath:
|
||||||
origin = np.dot(transform[0], origin) #rotate
|
origin = np.dot(transform[0], origin) #rotate
|
||||||
|
|
@ -236,28 +237,31 @@ class VlsiLayout:
|
||||||
uVector = np.dot(transform[1], uVector) #scale
|
uVector = np.dot(transform[1], uVector) #scale
|
||||||
vVector = np.dot(transform[1], vVector) #scale
|
vVector = np.dot(transform[1], vVector) #scale
|
||||||
origin = np.dot(transform[2], origin) #translate
|
origin = np.dot(transform[2], origin) #translate
|
||||||
#we don't need to do a translation on the basis vectors
|
#we don't need to do a translation on the basis vectors
|
||||||
#uVector = transform[2] * uVector #translate
|
#uVector = transform[2] * uVector #translate
|
||||||
#vVector = transform[2] * vVector #translate
|
#vVector = transform[2] * vVector #translate
|
||||||
#populate the xyTree with each structureName and coordinate space
|
#populate the xyTree with each structureName and coordinate space
|
||||||
self.xyTree.append((startingStructureName,origin,uVector,vVector))
|
self.xyTree.append((startingStructureName,origin,uVector,vVector))
|
||||||
self.traverseTheHierarchy(delegateFunction = addToXyTree)
|
self.traverseTheHierarchy(delegateFunction = addToXyTree)
|
||||||
|
|
||||||
def microns(self,userUnits):
|
def microns(self, userUnits):
|
||||||
"""Utility function to convert user units to microns"""
|
"""Utility function to convert user units to microns"""
|
||||||
userUnit = self.units[1]/self.units[0]
|
userUnit = self.units[1]/self.units[0]
|
||||||
userUnitsPerMicron = userUnit / (userunit)
|
userUnitsPerMicron = userUnit / userunit
|
||||||
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
||||||
return userUnits / layoutUnitsPerMicron
|
return userUnits / layoutUnitsPerMicron
|
||||||
|
|
||||||
def userUnits(self,microns):
|
def userUnits(self, microns):
|
||||||
"""Utility function to convert microns to user units"""
|
"""Utility function to convert microns to user units"""
|
||||||
userUnit = self.units[1]/self.units[0]
|
userUnit = self.units[1]/self.units[0]
|
||||||
#userUnitsPerMicron = userUnit / 1e-6
|
# userUnitsPerMicron = userUnit / 1e-6
|
||||||
userUnitsPerMicron = userUnit / (userUnit)
|
userUnitsPerMicron = userUnit / (userUnit)
|
||||||
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
||||||
#print("userUnit:",userUnit,"userUnitsPerMicron",userUnitsPerMicron,"layoutUnitsPerMicron",layoutUnitsPerMicron,[microns,microns*layoutUnitsPerMicron])
|
# print("userUnit:",userUnit,
|
||||||
return round(microns*layoutUnitsPerMicron,0)
|
# "userUnitsPerMicron",userUnitsPerMicron,
|
||||||
|
# "layoutUnitsPerMicron",layoutUnitsPerMicron,
|
||||||
|
# [microns,microns*layoutUnitsPerMicron])
|
||||||
|
return round(microns*layoutUnitsPerMicron, 0)
|
||||||
|
|
||||||
def changeRoot(self,newRoot, create=False):
|
def changeRoot(self,newRoot, create=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -266,7 +270,7 @@ class VlsiLayout:
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
debug.info(0,"DEBUG: GdsMill vlsiLayout: changeRoot: %s "%newRoot)
|
debug.info(0,"DEBUG: GdsMill vlsiLayout: changeRoot: %s "%newRoot)
|
||||||
|
|
||||||
# Determine if newRoot exists
|
# Determine if newRoot exists
|
||||||
# layoutToAdd (default) or nameOfLayout
|
# layoutToAdd (default) or nameOfLayout
|
||||||
if (newRoot == 0 | ((newRoot not in self.structures) & ~create)):
|
if (newRoot == 0 | ((newRoot not in self.structures) & ~create)):
|
||||||
|
|
@ -278,19 +282,19 @@ class VlsiLayout:
|
||||||
self.rootStructureName = newRoot
|
self.rootStructureName = newRoot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None):
|
def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None):
|
||||||
"""
|
"""
|
||||||
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:
|
if self.debug:
|
||||||
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type {0}, nameOfLayout {1}".format(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))
|
debug.info(0,"DEBUG: name={0} offset={1} mirror={2} rotate={3}".format(layoutToAdd.rootStructureName,offsetInMicrons, mirror, rotate))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Determine if we are instantiating the root design of
|
# Determine if we are instantiating the root design of
|
||||||
# layoutToAdd (default) or nameOfLayout
|
# layoutToAdd (default) or nameOfLayout
|
||||||
if nameOfLayout == 0:
|
if nameOfLayout == 0:
|
||||||
StructureFound = True
|
StructureFound = True
|
||||||
|
|
@ -299,7 +303,7 @@ class VlsiLayout:
|
||||||
StructureName = nameOfLayout #layoutToAdd
|
StructureName = nameOfLayout #layoutToAdd
|
||||||
StructureFound = False
|
StructureFound = False
|
||||||
for structure in layoutToAdd.structures:
|
for structure in layoutToAdd.structures:
|
||||||
if StructureName in structure:
|
if StructureName in structure:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
debug.info(1,"DEBUG: Structure %s Found"%StructureName)
|
debug.info(1,"DEBUG: Structure %s Found"%StructureName)
|
||||||
StructureFound = True
|
StructureFound = True
|
||||||
|
|
@ -307,7 +311,7 @@ class VlsiLayout:
|
||||||
debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName))
|
debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName))
|
||||||
|
|
||||||
|
|
||||||
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
||||||
# otherwise, if it is a text name of an internal structure, use it.
|
# otherwise, if it is a text name of an internal structure, use it.
|
||||||
|
|
||||||
if layoutToAdd != self:
|
if layoutToAdd != self:
|
||||||
|
|
@ -326,7 +330,7 @@ class VlsiLayout:
|
||||||
layoutToAddSref.coordinates = offsetInLayoutUnits
|
layoutToAddSref.coordinates = offsetInLayoutUnits
|
||||||
|
|
||||||
if mirror or rotate:
|
if mirror or rotate:
|
||||||
|
|
||||||
layoutToAddSref.transFlags = [0,0,0]
|
layoutToAddSref.transFlags = [0,0,0]
|
||||||
# transFlags = (mirror around x-axis, magnification, rotation)
|
# transFlags = (mirror around x-axis, magnification, rotation)
|
||||||
# If magnification or rotation is true, it is the flags are then
|
# If magnification or rotation is true, it is the flags are then
|
||||||
|
|
@ -352,8 +356,8 @@ class VlsiLayout:
|
||||||
|
|
||||||
#add the sref to the root structure
|
#add the sref to the root structure
|
||||||
self.structures[self.rootStructureName].srefs.append(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=0, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
|
||||||
"""
|
"""
|
||||||
Method to add a box to a layout
|
Method to add a box to a layout
|
||||||
"""
|
"""
|
||||||
|
|
@ -369,7 +373,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)
|
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
|
||||||
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),
|
||||||
|
|
@ -378,13 +382,12 @@ class VlsiLayout:
|
||||||
|
|
||||||
boundaryToAdd = GdsBoundary()
|
boundaryToAdd = GdsBoundary()
|
||||||
boundaryToAdd.drawingLayer = layerNumber
|
boundaryToAdd.drawingLayer = layerNumber
|
||||||
boundaryToAdd.dataType = 0
|
|
||||||
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.append(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=0, coordinates=[(0,0)], width=1.0):
|
||||||
"""
|
"""
|
||||||
Method to add a path to a layout
|
Method to add a path to a layout
|
||||||
"""
|
"""
|
||||||
|
|
@ -396,24 +399,21 @@ class VlsiLayout:
|
||||||
cY = self.userUnits(coordinate[1])
|
cY = self.userUnits(coordinate[1])
|
||||||
layoutUnitCoordinates.append((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.append(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=0, 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]))
|
||||||
textToAdd = GdsText()
|
textToAdd = GdsText()
|
||||||
textToAdd.drawingLayer = layerNumber
|
textToAdd.drawingLayer = layerNumber
|
||||||
textToAdd.purposeLayer = purposeNumber
|
textToAdd.purposeLayer = purposeNumber
|
||||||
textToAdd.dataType = 0
|
|
||||||
textToAdd.coordinates = [offsetInLayoutUnits]
|
textToAdd.coordinates = [offsetInLayoutUnits]
|
||||||
textToAdd.transFlags = [0,0,0]
|
textToAdd.transFlags = [0,0,0]
|
||||||
if(len(text)%2 == 1):
|
textToAdd.textString = self.padText(text)
|
||||||
text = text + '\x00'
|
|
||||||
textToAdd.textString = text
|
|
||||||
#textToAdd.transFlags[1] = 1
|
#textToAdd.transFlags[1] = 1
|
||||||
textToAdd.magFactor = magnification
|
textToAdd.magFactor = magnification
|
||||||
if rotate:
|
if rotate:
|
||||||
|
|
@ -421,7 +421,13 @@ class VlsiLayout:
|
||||||
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.append(textToAdd)
|
self.structures[self.rootStructureName].texts.append(textToAdd)
|
||||||
|
|
||||||
|
def padText(self, text):
|
||||||
|
if(len(text)%2 == 1):
|
||||||
|
return text + '\x00'
|
||||||
|
else:
|
||||||
|
return text
|
||||||
|
|
||||||
def isBounded(self,testPoint,startPoint,endPoint):
|
def isBounded(self,testPoint,startPoint,endPoint):
|
||||||
#these arguments are touples of (x,y) coordinates
|
#these arguments are touples of (x,y) coordinates
|
||||||
if testPoint == None:
|
if testPoint == None:
|
||||||
|
|
@ -433,7 +439,7 @@ class VlsiLayout:
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def intersectionPoint(self,startPoint1,endPoint1,startPoint2,endPoint2):
|
def intersectionPoint(self,startPoint1,endPoint1,startPoint2,endPoint2):
|
||||||
if((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])!=0):
|
if((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])!=0):
|
||||||
pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0])
|
pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0])
|
||||||
|
|
@ -453,7 +459,7 @@ class VlsiLayout:
|
||||||
newY = None
|
newY = None
|
||||||
elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])!=0):
|
elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])!=0):
|
||||||
qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0])
|
qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0])
|
||||||
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
|
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
|
||||||
newX=endPoint1[0]
|
newX=endPoint1[0]
|
||||||
newY=qSlope*newX+qIntercept
|
newY=qSlope*newX+qIntercept
|
||||||
elif((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])==0):
|
elif((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])==0):
|
||||||
|
|
@ -462,14 +468,14 @@ class VlsiLayout:
|
||||||
newX=endPoint2[0]
|
newX=endPoint2[0]
|
||||||
newY=pSlope*newX+pIntercept
|
newY=pSlope*newX+pIntercept
|
||||||
return (newX,newY)
|
return (newX,newY)
|
||||||
|
|
||||||
def isCollinear(self,testPoint,point1,point2):
|
def isCollinear(self,testPoint,point1,point2):
|
||||||
slope1 = (testPoint[1]-point1[1])/(testPoint[0]-point1[0])
|
slope1 = (testPoint[1]-point1[1])/(testPoint[0]-point1[0])
|
||||||
slope2 = (point2[1]-point1[1])/(point2[0]-point1[0])
|
slope2 = (point2[1]-point1[1])/(point2[0]-point1[0])
|
||||||
if slope1 == slope2:
|
if slope1 == slope2:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def doShapesIntersect(self,shape1Coordinates, shape2Coordinates):
|
def doShapesIntersect(self,shape1Coordinates, shape2Coordinates):
|
||||||
"""
|
"""
|
||||||
Utility function to determine if 2 arbitrary shapes intersect.
|
Utility function to determine if 2 arbitrary shapes intersect.
|
||||||
|
|
@ -486,7 +492,7 @@ class VlsiLayout:
|
||||||
if(self.isBounded(intersect,startPoint1,endPoint1) and self.isBounded(intersect,startPoint2,endPoint2)):
|
if(self.isBounded(intersect,startPoint1,endPoint1) and self.isBounded(intersect,startPoint2,endPoint2)):
|
||||||
return True #these shapes overlap!
|
return True #these shapes overlap!
|
||||||
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
|
Check if a point is contained in the shape
|
||||||
|
|
@ -511,7 +517,7 @@ class VlsiLayout:
|
||||||
pointCoordinates[1]<bottomBound):
|
pointCoordinates[1]<bottomBound):
|
||||||
return False
|
return False
|
||||||
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.
|
||||||
|
|
@ -520,8 +526,8 @@ class VlsiLayout:
|
||||||
if not self.isPointInsideOfBox(point,boxCoordinates):
|
if not self.isPointInsideOfBox(point,boxCoordinates):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def fillAreaDensity(self, layerToFill = 0, offsetInMicrons = (0,0), coverageWidth = 100.0, coverageHeight = 100.0, minSpacing = 0.22, blockSize = 1.0):
|
def fillAreaDensity(self, layerToFill = 0, offsetInMicrons = (0,0), coverageWidth = 100.0, coverageHeight = 100.0, minSpacing = 0.22, blockSize = 1.0):
|
||||||
effectiveBlock = blockSize+minSpacing
|
effectiveBlock = blockSize+minSpacing
|
||||||
widthInBlocks = int(coverageWidth/effectiveBlock)
|
widthInBlocks = int(coverageWidth/effectiveBlock)
|
||||||
|
|
@ -540,7 +546,7 @@ class VlsiLayout:
|
||||||
shiftedBoundaryCoordinates.append((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
|
||||||
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
||||||
if common:
|
if common:
|
||||||
self.tempPassFail = False
|
self.tempPassFail = False
|
||||||
|
|
@ -553,11 +559,11 @@ class VlsiLayout:
|
||||||
shiftedBoundaryCoordinates.append((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
|
||||||
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
||||||
if common:
|
if common:
|
||||||
self.tempPassFail = False
|
self.tempPassFail = False
|
||||||
|
|
||||||
for yIndex in range(0,heightInBlocks):
|
for yIndex in range(0,heightInBlocks):
|
||||||
for xIndex in range(0,widthInBlocks):
|
for xIndex in range(0,widthInBlocks):
|
||||||
percentDone = (float((yIndex*heightInBlocks)+xIndex) / (heightInBlocks*widthInBlocks))*100
|
percentDone = (float((yIndex*heightInBlocks)+xIndex) / (heightInBlocks*widthInBlocks))*100
|
||||||
|
|
@ -576,7 +582,7 @@ class VlsiLayout:
|
||||||
passFailRecord.append(self.tempPassFail)
|
passFailRecord.append(self.tempPassFail)
|
||||||
print("Percent Complete:"+str(percentDone))
|
print("Percent Complete:"+str(percentDone))
|
||||||
|
|
||||||
|
|
||||||
passFailIndex=0
|
passFailIndex=0
|
||||||
for yIndex in range(0,heightInBlocks):
|
for yIndex in range(0,heightInBlocks):
|
||||||
for xIndex in range(0,widthInBlocks):
|
for xIndex in range(0,widthInBlocks):
|
||||||
|
|
@ -587,41 +593,50 @@ class VlsiLayout:
|
||||||
passFailIndex += 1
|
passFailIndex += 1
|
||||||
print("Done\n\n")
|
print("Done\n\n")
|
||||||
|
|
||||||
def getLayoutBorder(self,borderlayer):
|
def getLayoutBorder(self, lpp):
|
||||||
cellSizeMicron=None
|
cellSizeMicron = None
|
||||||
for boundary in self.structures[self.rootStructureName].boundaries:
|
for boundary in self.structures[self.rootStructureName].boundaries:
|
||||||
if boundary.drawingLayer==borderlayer:
|
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
|
||||||
|
lpp):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
debug.info(1,"Find border "+str(boundary.coordinates))
|
debug.info(1, "Find border "+str(boundary.coordinates))
|
||||||
left_bottom=boundary.coordinates[0]
|
left_bottom = boundary.coordinates[0]
|
||||||
right_top=boundary.coordinates[2]
|
right_top = boundary.coordinates[2]
|
||||||
cellSize=[right_top[0]-left_bottom[0],right_top[1]-left_bottom[1]]
|
cellSize = [right_top[0]-left_bottom[0],
|
||||||
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
|
right_top[1]-left_bottom[1]]
|
||||||
if not(cellSizeMicron):
|
cellSizeMicron = [cellSize[0]*self.units[0],
|
||||||
print("Error: "+str(self.rootStructureName)+".cell_size information not found yet")
|
cellSize[1]*self.units[0]]
|
||||||
|
debug.check(cellSizeMicron,
|
||||||
|
"Error: "+str(self.rootStructureName)+".cell_size information not found yet")
|
||||||
|
|
||||||
return cellSizeMicron
|
return cellSizeMicron
|
||||||
|
|
||||||
def measureSize(self,startStructure):
|
def measureSize(self, startStructure):
|
||||||
self.rootStructureName=startStructure
|
self.rootStructureName = self.padText(startStructure)
|
||||||
self.populateCoordinateMap()
|
self.populateCoordinateMap()
|
||||||
cellBoundary = [None, None, None, None]
|
cellBoundary = [None, None, None, None]
|
||||||
for TreeUnit in self.xyTree:
|
for TreeUnit in self.xyTree:
|
||||||
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
|
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
|
||||||
cellSize=[cellBoundary[2]-cellBoundary[0],cellBoundary[3]-cellBoundary[1]]
|
cellSize = [cellBoundary[2]-cellBoundary[0],
|
||||||
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
|
cellBoundary[3]-cellBoundary[1]]
|
||||||
|
cellSizeMicron = [cellSize[0]*self.units[0],
|
||||||
|
cellSize[1]*self.units[0]]
|
||||||
return cellSizeMicron
|
return cellSizeMicron
|
||||||
|
|
||||||
def measureBoundary(self,startStructure):
|
def measureBoundary(self, startStructure):
|
||||||
self.rootStructureName=startStructure
|
self.rootStructureName = self.padText(startStructure)
|
||||||
self.populateCoordinateMap()
|
self.populateCoordinateMap()
|
||||||
cellBoundary = [None, None, None, None]
|
cellBoundary = [None, None, None, None]
|
||||||
for TreeUnit in self.xyTree:
|
for TreeUnit in self.xyTree:
|
||||||
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
|
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
|
||||||
return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]],
|
return [[self.units[0]*cellBoundary[0],
|
||||||
[self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]]
|
self.units[0]*cellBoundary[1]],
|
||||||
|
[self.units[0]*cellBoundary[2],
|
||||||
def measureSizeInStructure(self,structure,cellBoundary):
|
self.units[0]*cellBoundary[3]]]
|
||||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
|
||||||
|
def measureSizeInStructure(self, structure, cellBoundary):
|
||||||
|
(structureName, structureOrigin,
|
||||||
|
structureuVector, structurevVector) = structure
|
||||||
for boundary in self.structures[str(structureName)].boundaries:
|
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]
|
||||||
|
|
@ -631,7 +646,7 @@ class VlsiLayout:
|
||||||
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 any are None
|
# If any are None
|
||||||
|
|
@ -648,17 +663,17 @@ class VlsiLayout:
|
||||||
cellBoundary[3]=right_top_Y
|
cellBoundary[3]=right_top_Y
|
||||||
return cellBoundary
|
return cellBoundary
|
||||||
|
|
||||||
|
def getTexts(self, lpp):
|
||||||
def getTexts(self, layer):
|
|
||||||
"""
|
"""
|
||||||
Get all of the labels on a given layer only at the root level.
|
Get all of the labels on a given layer only at the root level.
|
||||||
"""
|
"""
|
||||||
text_list = []
|
text_list = []
|
||||||
for Text in self.structures[self.rootStructureName].texts:
|
for Text in self.structures[self.rootStructureName].texts:
|
||||||
if Text.drawingLayer == layer:
|
if sameLPP((Text.drawingLayer, Text.purposeLayer),
|
||||||
|
lpp):
|
||||||
text_list.append(Text)
|
text_list.append(Text)
|
||||||
return text_list
|
return text_list
|
||||||
|
|
||||||
def getPinShape(self, pin_name):
|
def getPinShape(self, pin_name):
|
||||||
"""
|
"""
|
||||||
Search for a pin label and return the largest enclosing rectangle
|
Search for a pin label and return the largest enclosing rectangle
|
||||||
|
|
@ -671,15 +686,15 @@ class VlsiLayout:
|
||||||
max_pin = None
|
max_pin = None
|
||||||
max_area = 0
|
max_area = 0
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
(layer,boundary) = pin
|
(layer, boundary) = pin
|
||||||
new_area = boundaryArea(boundary)
|
new_area = boundaryArea(boundary)
|
||||||
if max_pin == None or new_area>max_area:
|
if not max_pin or new_area > max_area:
|
||||||
max_pin = pin
|
max_pin = pin
|
||||||
max_area = new_area
|
max_area = new_area
|
||||||
max_pins.append(max_pin)
|
max_pins.append(max_pin)
|
||||||
|
|
||||||
return max_pins
|
return max_pins
|
||||||
|
|
||||||
|
|
||||||
def getAllPinShapes(self, pin_name):
|
def getAllPinShapes(self, pin_name):
|
||||||
"""
|
"""
|
||||||
|
|
@ -690,32 +705,33 @@ class VlsiLayout:
|
||||||
pin_map = self.pins[pin_name]
|
pin_map = self.pins[pin_name]
|
||||||
for pin_list in pin_map:
|
for pin_list in pin_map:
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
(pin_layer, boundary) = pin
|
(pin_layer, boundary) = pin
|
||||||
shape_list.append(pin)
|
shape_list.append(pin)
|
||||||
|
|
||||||
return shape_list
|
return shape_list
|
||||||
|
|
||||||
|
|
||||||
def processLabelPins(self, layer):
|
def processLabelPins(self, lpp):
|
||||||
"""
|
"""
|
||||||
Find all text labels and create a map to a list of shapes that
|
Find all text labels and create a map to a list of shapes that
|
||||||
they enclose on the given layer.
|
they enclose on the given layer.
|
||||||
"""
|
"""
|
||||||
# Get the labels on a layer in the root level
|
# Get the labels on a layer in the root level
|
||||||
labels = self.getTexts(layer)
|
labels = self.getTexts(lpp)
|
||||||
|
|
||||||
# Get all of the shapes on the layer at all levels
|
# Get all of the shapes on the layer at all levels
|
||||||
# and transform them to the current level
|
# and transform them to the current level
|
||||||
shapes = self.getAllShapes(layer)
|
shapes = self.getAllShapes(lpp)
|
||||||
|
|
||||||
for label in labels:
|
for label in labels:
|
||||||
label_coordinate = label.coordinates[0]
|
label_coordinate = label.coordinates[0]
|
||||||
user_coordinate = [x*self.units[0] for x in label_coordinate]
|
user_coordinate = [x*self.units[0] for x in label_coordinate]
|
||||||
pin_shapes = []
|
pin_shapes = []
|
||||||
for boundary in shapes:
|
for boundary in shapes:
|
||||||
if self.labelInRectangle(user_coordinate,boundary):
|
if self.labelInRectangle(user_coordinate, boundary):
|
||||||
pin_shapes.append((layer, boundary))
|
pin_shapes.append((lpp, boundary))
|
||||||
|
|
||||||
label_text = label.textString
|
label_text = label.textString
|
||||||
|
|
||||||
# Remove the padding if it exists
|
# Remove the padding if it exists
|
||||||
if label_text[-1] == "\x00":
|
if label_text[-1] == "\x00":
|
||||||
label_text = label_text[0:-1]
|
label_text = label_text[0:-1]
|
||||||
|
|
@ -725,81 +741,97 @@ class VlsiLayout:
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.pins[label_text] = []
|
self.pins[label_text] = []
|
||||||
self.pins[label_text].append(pin_shapes)
|
self.pins[label_text].append(pin_shapes)
|
||||||
|
|
||||||
|
def getBlockages(self, lpp):
|
||||||
def getBlockages(self,layer):
|
|
||||||
"""
|
"""
|
||||||
Return all blockages on a given layer in [coordinate 1, coordinate 2,...] format and
|
Return all blockages on a given layer in
|
||||||
|
[coordinate 1, coordinate 2,...] format and
|
||||||
user units.
|
user units.
|
||||||
"""
|
"""
|
||||||
blockages = []
|
blockages = []
|
||||||
|
|
||||||
shapes = self.getAllShapes(layer)
|
shapes = self.getAllShapes(lpp)
|
||||||
for boundary in shapes:
|
for boundary in shapes:
|
||||||
vectors = []
|
vectors = []
|
||||||
for i in range(0,len(boundary),2):
|
for i in range(0, len(boundary), 2):
|
||||||
vectors.append(vector(boundary[i],boundary[i+1]))
|
vectors.append((boundary[i], boundary[i+1]))
|
||||||
blockages.append(vectors)
|
blockages.append(vectors)
|
||||||
return blockages
|
|
||||||
|
|
||||||
def getAllShapes(self,layer):
|
return blockages
|
||||||
|
|
||||||
|
def getAllShapes(self, lpp):
|
||||||
"""
|
"""
|
||||||
Return all shapes on a given layer in [llx, lly, urx, ury] format and user units for rectangles
|
Return all shapes on a given layer in [llx, lly, urx, ury]
|
||||||
and [coordinate 1, coordinate 2,...] format and user units for polygons.
|
format and user units for rectangles
|
||||||
|
and [coordinate 1, coordinate 2,...] format and user
|
||||||
|
units for polygons.
|
||||||
"""
|
"""
|
||||||
boundaries = set()
|
boundaries = set()
|
||||||
for TreeUnit in self.xyTree:
|
for TreeUnit in self.xyTree:
|
||||||
#print(TreeUnit[0])
|
# print(TreeUnit[0])
|
||||||
boundaries.update(self.getShapesInStructure(layer,TreeUnit))
|
boundaries.update(self.getShapesInStructure(lpp, TreeUnit))
|
||||||
|
|
||||||
# Convert to user units
|
# Convert to user units
|
||||||
user_boundaries = []
|
user_boundaries = []
|
||||||
for boundary in boundaries:
|
for boundary in boundaries:
|
||||||
boundaries_list = []
|
boundaries_list = []
|
||||||
for i in range(0,len(boundary)):
|
for i in range(0, len(boundary)):
|
||||||
boundaries_list.append(boundary[i]*self.units[0])
|
boundaries_list.append(boundary[i]*self.units[0])
|
||||||
user_boundaries.append(boundaries_list)
|
user_boundaries.append(boundaries_list)
|
||||||
return user_boundaries
|
return user_boundaries
|
||||||
|
|
||||||
|
def getShapesInStructure(self, lpp, structure):
|
||||||
def getShapesInStructure(self,layer,structure):
|
|
||||||
"""
|
|
||||||
Go through all the shapes in a structure and return the list of shapes in
|
|
||||||
the form [llx, lly, urx, ury] for rectangles and [coordinate 1, coordinate 2,...] for polygons.
|
|
||||||
"""
|
"""
|
||||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
Go through all the shapes in a structure and
|
||||||
#print(structureName,"u",structureuVector.transpose(),"v",structurevVector.transpose(),"o",structureOrigin.transpose())
|
return the list of shapes in
|
||||||
|
the form [llx, lly, urx, ury] for rectangles
|
||||||
|
and [coordinate 1, coordinate 2,...] for polygons.
|
||||||
|
"""
|
||||||
|
(structureName, structureOrigin,
|
||||||
|
structureuVector, structurevVector) = structure
|
||||||
|
# print(structureName,
|
||||||
|
# "u", structureuVector.transpose(),
|
||||||
|
# "v",structurevVector.transpose(),
|
||||||
|
# "o",structureOrigin.transpose())
|
||||||
boundaries = []
|
boundaries = []
|
||||||
for boundary in self.structures[str(structureName)].boundaries:
|
for boundary in self.structures[str(structureName)].boundaries:
|
||||||
if layer==boundary.drawingLayer:
|
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
|
||||||
if len(boundary.coordinates)!=5:
|
lpp):
|
||||||
|
if len(boundary.coordinates) != 5:
|
||||||
# if shape is a polygon (used in DFF)
|
# if shape is a polygon (used in DFF)
|
||||||
boundaryPolygon = []
|
boundaryPolygon = []
|
||||||
# Polygon is a list of coordinates going ccw
|
# Polygon is a list of coordinates going ccw
|
||||||
for coord in range(0,len(boundary.coordinates)):
|
for coord in range(0, len(boundary.coordinates)):
|
||||||
boundaryPolygon.append(boundary.coordinates[coord][0])
|
boundaryPolygon.append(boundary.coordinates[coord][0])
|
||||||
boundaryPolygon.append(boundary.coordinates[coord][1])
|
boundaryPolygon.append(boundary.coordinates[coord][1])
|
||||||
# perform the rotation
|
# perform the rotation
|
||||||
boundaryPolygon=self.transformPolygon(boundaryPolygon,structureuVector,structurevVector)
|
boundaryPolygon = self.transformPolygon(boundaryPolygon,
|
||||||
# add the offset
|
structureuVector,
|
||||||
|
structurevVector)
|
||||||
|
# add the offset
|
||||||
polygon = []
|
polygon = []
|
||||||
for i in range(0,len(boundaryPolygon),2):
|
for i in range(0, len(boundaryPolygon), 2):
|
||||||
polygon.append(boundaryPolygon[i]+structureOrigin[0].item())
|
polygon.append(boundaryPolygon[i] + structureOrigin[0].item())
|
||||||
polygon.append(boundaryPolygon[i+1]+structureOrigin[1].item())
|
polygon.append(boundaryPolygon[i+1] + structureOrigin[1].item())
|
||||||
# make it a tuple
|
# make it a tuple
|
||||||
polygon = tuple(polygon)
|
polygon = tuple(polygon)
|
||||||
boundaries.append(polygon)
|
boundaries.append(polygon)
|
||||||
else:
|
else:
|
||||||
# else shape is a rectangle
|
# else shape is a rectangle
|
||||||
left_bottom=boundary.coordinates[0]
|
left_bottom = boundary.coordinates[0]
|
||||||
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
|
# perform the rotation
|
||||||
boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
|
boundaryRect = self.transformRectangle(boundaryRect,
|
||||||
|
structureuVector,
|
||||||
|
structurevVector)
|
||||||
# add the offset and make it a tuple
|
# add the offset and make it a tuple
|
||||||
boundaryRect=(boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
|
boundaryRect = (boundaryRect[0]+structureOrigin[0].item(),
|
||||||
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item())
|
boundaryRect[1]+structureOrigin[1].item(),
|
||||||
|
boundaryRect[2]+structureOrigin[0].item(),
|
||||||
|
boundaryRect[3]+structureOrigin[1].item())
|
||||||
boundaries.append(boundaryRect)
|
boundaries.append(boundaryRect)
|
||||||
return boundaries
|
return boundaries
|
||||||
|
|
||||||
|
|
@ -839,7 +871,7 @@ class VlsiLayout:
|
||||||
"""
|
"""
|
||||||
Rotate a coordinate in space.
|
Rotate a coordinate in space.
|
||||||
"""
|
"""
|
||||||
# MRG: 9/3/18 Incorrect matrix multiplication!
|
# MRG: 9/3/18 Incorrect matrix multiplication!
|
||||||
# This is fixed to be:
|
# This is fixed to be:
|
||||||
# |u[0] v[0]| |x| |x'|
|
# |u[0] v[0]| |x| |x'|
|
||||||
# |u[1] v[1]|x|y|=|y'|
|
# |u[1] v[1]|x|y|=|y'|
|
||||||
|
|
@ -861,11 +893,21 @@ class VlsiLayout:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def sameLPP(lpp1, lpp2):
|
||||||
|
"""
|
||||||
|
Check if the layers and purposes are the same.
|
||||||
|
Ignore if purpose is a None.
|
||||||
|
"""
|
||||||
|
if lpp1[1] == None or lpp2[1] == None:
|
||||||
|
return lpp1[0] == lpp2[0]
|
||||||
|
|
||||||
|
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|
||||||
|
|
||||||
|
|
||||||
def boundaryArea(A):
|
def boundaryArea(A):
|
||||||
"""
|
"""
|
||||||
Returns boundary area for sorting.
|
Returns boundary area for sorting.
|
||||||
"""
|
"""
|
||||||
area_A=(A[2]-A[0])*(A[3]-A[1])
|
area_A=(A[2]-A[0])*(A[3]-A[1])
|
||||||
return area_A
|
return area_A
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,12 @@ import sram
|
||||||
class fake_sram(sram.sram):
|
class fake_sram(sram.sram):
|
||||||
""" This is an SRAM that doesn't actually create itself, just computes
|
""" This is an SRAM that doesn't actually create itself, just computes
|
||||||
the sizes. """
|
the sizes. """
|
||||||
def __init__(self, word_size, num_words, num_banks, name):
|
def __init__(self, word_size, num_words, num_banks, name, num_spare_rows):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.word_size = word_size
|
self.word_size = word_size
|
||||||
self.num_words = num_words
|
self.num_words = num_words
|
||||||
self.num_banks = num_banks
|
self.num_banks = num_banks
|
||||||
|
self.num_spare_rows = num_spare_rows
|
||||||
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 = self.mod_bitcell()
|
self.bitcell = self.mod_bitcell()
|
||||||
|
|
@ -75,7 +76,10 @@ d.period = period
|
||||||
# Set the load of outputs and slew of inputs
|
# Set the load of outputs and slew of inputs
|
||||||
d.set_load_slew(load,slew)
|
d.set_load_slew(load,slew)
|
||||||
# Set the probe address/bit
|
# Set the probe address/bit
|
||||||
probe_address = "1" * sram.addr_size
|
if (self.num_spare_rows == 0):
|
||||||
|
probe_address = "1" * sram.addr_size
|
||||||
|
else:
|
||||||
|
probe_address = "0" + ("1" * sram.addr_size - 1)
|
||||||
probe_data = sram.word_size - 1
|
probe_data = sram.word_size - 1
|
||||||
d.set_probe(probe_address, probe_data)
|
d.set_probe(probe_address, probe_data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
"""
|
"""
|
||||||
This is called globals.py, but it actually parses all the arguments and performs
|
This is called globals.py, but it actually parses all the arguments
|
||||||
the global OpenRAM setup as well.
|
and performs the global OpenRAM setup as well.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import debug
|
import debug
|
||||||
|
|
@ -19,12 +19,13 @@ import re
|
||||||
import copy
|
import copy
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
VERSION = "1.1.2"
|
VERSION = "1.1.5"
|
||||||
NAME = "OpenRAM v{}".format(VERSION)
|
NAME = "OpenRAM v{}".format(VERSION)
|
||||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||||
|
|
||||||
OPTS = options.options()
|
OPTS = options.options()
|
||||||
CHECKPOINT_OPTS=None
|
CHECKPOINT_OPTS = None
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
""" Parse the optional arguments for OpenRAM """
|
""" Parse the optional arguments for OpenRAM """
|
||||||
|
|
@ -32,27 +33,55 @@ def parse_args():
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
option_list = {
|
option_list = {
|
||||||
optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex",
|
optparse.make_option("-b",
|
||||||
|
"--backannotated",
|
||||||
|
action="store_true",
|
||||||
|
dest="use_pex",
|
||||||
help="Back annotate simulation"),
|
help="Back annotate simulation"),
|
||||||
optparse.make_option("-o", "--output", dest="output_name",
|
optparse.make_option("-o",
|
||||||
help="Base output file name(s) prefix", metavar="FILE"),
|
"--output",
|
||||||
optparse.make_option("-p", "--outpath", dest="output_path",
|
dest="output_name",
|
||||||
|
help="Base output file name(s) prefix",
|
||||||
|
metavar="FILE"),
|
||||||
|
optparse.make_option("-p", "--outpath",
|
||||||
|
dest="output_path",
|
||||||
help="Output file(s) location"),
|
help="Output file(s) location"),
|
||||||
optparse.make_option("-i", "--inlinecheck", action="store_true",
|
optparse.make_option("-i",
|
||||||
help="Enable inline LVS/DRC checks", dest="inline_lvsdrc"),
|
"--inlinecheck",
|
||||||
optparse.make_option("-n", "--nocheck", action="store_false",
|
action="store_true",
|
||||||
help="Disable all LVS/DRC checks", dest="check_lvsdrc"),
|
help="Enable inline LVS/DRC checks",
|
||||||
optparse.make_option("-v", "--verbose", action="count", dest="debug_level",
|
dest="inline_lvsdrc"),
|
||||||
|
optparse.make_option("-n", "--nocheck",
|
||||||
|
action="store_false",
|
||||||
|
help="Disable all LVS/DRC checks",
|
||||||
|
dest="check_lvsdrc"),
|
||||||
|
optparse.make_option("-v",
|
||||||
|
"--verbose",
|
||||||
|
action="count",
|
||||||
|
dest="debug_level",
|
||||||
help="Increase the verbosity level"),
|
help="Increase the verbosity level"),
|
||||||
optparse.make_option("-t", "--tech", dest="tech_name",
|
optparse.make_option("-t",
|
||||||
|
"--tech",
|
||||||
|
dest="tech_name",
|
||||||
help="Technology name"),
|
help="Technology name"),
|
||||||
optparse.make_option("-s", "--spice", dest="spice_name",
|
optparse.make_option("-s",
|
||||||
|
"--spice",
|
||||||
|
dest="spice_name",
|
||||||
help="Spice simulator executable name"),
|
help="Spice simulator executable name"),
|
||||||
optparse.make_option("-r", "--remove_netlist_trimming", action="store_false", dest="trim_netlist",
|
optparse.make_option("-r",
|
||||||
|
"--remove_netlist_trimming",
|
||||||
|
action="store_false",
|
||||||
|
dest="trim_netlist",
|
||||||
help="Disable removal of noncritical memory cells during characterization"),
|
help="Disable removal of noncritical memory cells during characterization"),
|
||||||
optparse.make_option("-c", "--characterize", action="store_false", dest="analytical_delay",
|
optparse.make_option("-c",
|
||||||
|
"--characterize",
|
||||||
|
action="store_false",
|
||||||
|
dest="analytical_delay",
|
||||||
help="Perform characterization to calculate delays (default is analytical models)"),
|
help="Perform characterization to calculate delays (default is analytical models)"),
|
||||||
optparse.make_option("-d", "--dontpurge", action="store_false", dest="purge_temp",
|
optparse.make_option("-d",
|
||||||
|
"--dontpurge",
|
||||||
|
action="store_false",
|
||||||
|
dest="purge_temp",
|
||||||
help="Don't purge the contents of the temp directory after a successful run")
|
help="Don't purge the contents of the temp directory after a successful run")
|
||||||
# -h --help is implicit.
|
# -h --help is implicit.
|
||||||
}
|
}
|
||||||
|
|
@ -70,9 +99,13 @@ def parse_args():
|
||||||
# Alias SCMOS to 180nm
|
# Alias SCMOS to 180nm
|
||||||
if OPTS.tech_name == "scmos":
|
if OPTS.tech_name == "scmos":
|
||||||
OPTS.tech_name = "scn4m_subm"
|
OPTS.tech_name = "scn4m_subm"
|
||||||
|
# Alias s8 to sky130
|
||||||
|
if OPTS.tech_name == "s8":
|
||||||
|
OPTS.tech_name = "sky130"
|
||||||
|
|
||||||
return (options, args)
|
return (options, args)
|
||||||
|
|
||||||
|
|
||||||
def print_banner():
|
def print_banner():
|
||||||
""" Conditionally print the banner to stdout """
|
""" Conditionally print the banner to stdout """
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
@ -117,13 +150,13 @@ def check_versions():
|
||||||
except:
|
except:
|
||||||
OPTS.coverage = 0
|
OPTS.coverage = 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. """
|
||||||
|
|
||||||
|
|
||||||
check_versions()
|
check_versions()
|
||||||
|
|
||||||
debug.info(1,"Initializing OpenRAM...")
|
debug.info(1, "Initializing OpenRAM...")
|
||||||
|
|
||||||
setup_paths()
|
setup_paths()
|
||||||
|
|
||||||
|
|
@ -138,18 +171,17 @@ def init_openram(config_file, is_unit_test=True):
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
factory.reset()
|
factory.reset()
|
||||||
|
|
||||||
setup_bitcell()
|
|
||||||
|
|
||||||
global OPTS
|
global OPTS
|
||||||
global CHECKPOINT_OPTS
|
global CHECKPOINT_OPTS
|
||||||
|
|
||||||
# This is a hack. If we are running a unit test and have checkpointed
|
# This is a hack. If we are running a unit test and have checkpointed
|
||||||
# the options, load them rather than reading the config file.
|
# the options, load them rather than reading the config file.
|
||||||
# This way, the configuration is reloaded at the start of every unit test.
|
# This way, the configuration is reloaded at the start of every unit test.
|
||||||
# If a unit test fails, we don't have to worry about restoring the old config values
|
# If a unit test fails,
|
||||||
|
# we don't have to worry about restoring the old config values
|
||||||
# that may have been tested.
|
# that may have been tested.
|
||||||
if is_unit_test and CHECKPOINT_OPTS:
|
if is_unit_test and CHECKPOINT_OPTS:
|
||||||
OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy()
|
OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Import these to find the executables for checkpointing
|
# Import these to find the executables for checkpointing
|
||||||
|
|
@ -159,7 +191,8 @@ def init_openram(config_file, is_unit_test=True):
|
||||||
# after each unit test
|
# after each unit test
|
||||||
if not CHECKPOINT_OPTS:
|
if not CHECKPOINT_OPTS:
|
||||||
CHECKPOINT_OPTS = copy.copy(OPTS)
|
CHECKPOINT_OPTS = copy.copy(OPTS)
|
||||||
|
|
||||||
|
|
||||||
def setup_bitcell():
|
def setup_bitcell():
|
||||||
"""
|
"""
|
||||||
Determine the correct custom or parameterized bitcell for the design.
|
Determine the correct custom or parameterized bitcell for the design.
|
||||||
|
|
@ -169,44 +202,36 @@ def setup_bitcell():
|
||||||
# If we have non-1rw ports,
|
# If we have non-1rw ports,
|
||||||
# and the user didn't over-ride the bitcell manually,
|
# and the user didn't over-ride the bitcell manually,
|
||||||
# figure out the right bitcell to use
|
# figure out the right bitcell to use
|
||||||
if (OPTS.bitcell=="bitcell"):
|
if (OPTS.bitcell == "bitcell"):
|
||||||
|
|
||||||
if (OPTS.num_rw_ports==1 and OPTS.num_w_ports==0 and OPTS.num_r_ports==0):
|
if (OPTS.num_rw_ports == 1 and OPTS.num_w_ports == 0 and OPTS.num_r_ports == 0):
|
||||||
OPTS.bitcell = "bitcell"
|
OPTS.bitcell = "bitcell"
|
||||||
OPTS.replica_bitcell = "replica_bitcell"
|
OPTS.replica_bitcell = "replica_bitcell"
|
||||||
OPTS.dummy_bitcell = "dummy_bitcell"
|
OPTS.dummy_bitcell = "dummy_bitcell"
|
||||||
else:
|
else:
|
||||||
ports = ""
|
ports = ""
|
||||||
if OPTS.num_rw_ports>0:
|
if OPTS.num_rw_ports > 0:
|
||||||
ports += "{}rw_".format(OPTS.num_rw_ports)
|
ports += "{}rw_".format(OPTS.num_rw_ports)
|
||||||
if OPTS.num_w_ports>0:
|
if OPTS.num_w_ports > 0:
|
||||||
ports += "{}w_".format(OPTS.num_w_ports)
|
ports += "{}w_".format(OPTS.num_w_ports)
|
||||||
if OPTS.num_r_ports>0:
|
if OPTS.num_r_ports > 0:
|
||||||
ports += "{}r".format(OPTS.num_r_ports)
|
ports += "{}r".format(OPTS.num_r_ports)
|
||||||
|
|
||||||
OPTS.bitcell = "bitcell_"+ports
|
if ports != "":
|
||||||
OPTS.replica_bitcell = "replica_bitcell_"+ports
|
OPTS.bitcell_suffix = "_" + ports
|
||||||
OPTS.dummy_bitcell = "dummy_bitcell_"+ports
|
OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix
|
||||||
else:
|
|
||||||
OPTS.replica_bitcell = "replica_" + OPTS.bitcell
|
|
||||||
OPTS.replica_bitcell = "dummy_" + OPTS.bitcell
|
|
||||||
|
|
||||||
# See if bitcell exists
|
# See if bitcell exists
|
||||||
from importlib import find_loader
|
|
||||||
try:
|
try:
|
||||||
__import__(OPTS.bitcell)
|
__import__(OPTS.bitcell)
|
||||||
__import__(OPTS.replica_bitcell)
|
|
||||||
__import__(OPTS.dummy_bitcell)
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Use the pbitcell if we couldn't find a custom bitcell
|
# Use the pbitcell if we couldn't find a custom bitcell
|
||||||
# or its custom replica bitcell
|
# or its custom replica bitcell
|
||||||
# Use the pbitcell (and give a warning if not in unit test mode)
|
# Use the pbitcell (and give a warning if not in unit test mode)
|
||||||
OPTS.bitcell = "pbitcell"
|
OPTS.bitcell = "pbitcell"
|
||||||
OPTS.replica_bitcell = "replica_pbitcell"
|
|
||||||
OPTS.replica_bitcell = "dummy_pbitcell"
|
|
||||||
if not OPTS.is_unit_test:
|
if not OPTS.is_unit_test:
|
||||||
debug.warning("Using the parameterized bitcell which may have suboptimal density.")
|
debug.warning("Using the parameterized bitcell which may have suboptimal density.")
|
||||||
debug.info(1,"Using bitcell: {}".format(OPTS.bitcell))
|
debug.info(1, "Using bitcell: {}".format(OPTS.bitcell))
|
||||||
|
|
||||||
|
|
||||||
def get_tool(tool_type, preferences, default_name=None):
|
def get_tool(tool_type, preferences, default_name=None):
|
||||||
|
|
@ -215,63 +240,75 @@ def get_tool(tool_type, preferences, default_name=None):
|
||||||
one selected and its full path. If default is specified,
|
one selected and its full path. If default is specified,
|
||||||
find that one only and error otherwise.
|
find that one only and error otherwise.
|
||||||
"""
|
"""
|
||||||
debug.info(2,"Finding {} tool...".format(tool_type))
|
debug.info(2, "Finding {} tool...".format(tool_type))
|
||||||
|
|
||||||
if default_name:
|
if default_name:
|
||||||
exe_name=find_exe(default_name)
|
exe_name = find_exe(default_name)
|
||||||
if exe_name == None:
|
if exe_name == None:
|
||||||
debug.error("{0} not found. Cannot find {1} tool.".format(default_name,tool_type),2)
|
debug.error("{0} not found. Cannot find {1} tool.".format(default_name,
|
||||||
|
tool_type),
|
||||||
|
2)
|
||||||
else:
|
else:
|
||||||
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
|
||||||
return(default_name,exe_name)
|
return(default_name, exe_name)
|
||||||
else:
|
else:
|
||||||
for name in preferences:
|
for name in preferences:
|
||||||
exe_name = find_exe(name)
|
exe_name = find_exe(name)
|
||||||
if exe_name != None:
|
if exe_name != None:
|
||||||
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
|
||||||
return(name,exe_name)
|
return(name, exe_name)
|
||||||
else:
|
else:
|
||||||
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
|
debug.info(1,
|
||||||
|
"Could not find {0}, trying next {1} tool.".format(name,
|
||||||
|
tool_type))
|
||||||
else:
|
else:
|
||||||
return(None,"")
|
return(None, "")
|
||||||
|
|
||||||
|
|
||||||
def read_config(config_file, is_unit_test=True):
|
def read_config(config_file, is_unit_test=True):
|
||||||
"""
|
"""
|
||||||
Read the configuration file that defines a few parameters. The
|
Read the configuration file that defines a few parameters. The
|
||||||
config file is just a Python file that defines some config
|
config file is just a Python file that defines some config
|
||||||
options. This will only actually get read the first time. Subsequent
|
options. This will only actually get read the first time. Subsequent
|
||||||
reads will just restore the previous copy (ask mrg)
|
reads will just restore the previous copy (ask mrg)
|
||||||
"""
|
"""
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
# Create a full path relative to current dir unless it is already an abs path
|
# it is already not an abs path, make it one
|
||||||
if not os.path.isabs(config_file):
|
if not os.path.isabs(config_file):
|
||||||
config_file = os.getcwd() + "/" + config_file
|
config_file = os.getcwd() + "/" + config_file
|
||||||
|
|
||||||
# Make it a python file if the base name was only given
|
# Make it a python file if the base name was only given
|
||||||
config_file = re.sub(r'\.py$', "", config_file)
|
config_file = re.sub(r'\.py$', "", config_file)
|
||||||
|
|
||||||
|
|
||||||
# Expand the user if it is used
|
# Expand the user if it is used
|
||||||
config_file = os.path.expanduser(config_file)
|
config_file = os.path.expanduser(config_file)
|
||||||
OPTS.config_file = config_file
|
|
||||||
# Add the path to the system path so we can import things in the other directory
|
OPTS.config_file = config_file + ".py"
|
||||||
|
# Add the path to the system path
|
||||||
|
# so we can import things in the other directory
|
||||||
dir_name = os.path.dirname(config_file)
|
dir_name = os.path.dirname(config_file)
|
||||||
file_name = os.path.basename(config_file)
|
module_name = os.path.basename(config_file)
|
||||||
|
|
||||||
# Prepend the path to avoid if we are using the example config
|
# Prepend the path to avoid if we are using the example config
|
||||||
sys.path.insert(0,dir_name)
|
sys.path.insert(0, dir_name)
|
||||||
# Import the configuration file of which modules to use
|
# Import the configuration file of which modules to use
|
||||||
debug.info(1, "Configuration file is " + config_file + ".py")
|
debug.info(1, "Configuration file is " + config_file + ".py")
|
||||||
try:
|
try:
|
||||||
config = importlib.import_module(file_name)
|
config = importlib.import_module(module_name)
|
||||||
except:
|
except:
|
||||||
debug.error("Unable to read configuration file: {0}".format(config_file),2)
|
debug.error("Unable to read configuration file: {0}".format(config_file),2)
|
||||||
|
|
||||||
for k,v in config.__dict__.items():
|
OPTS.overridden = {}
|
||||||
|
for k, v in config.__dict__.items():
|
||||||
# The command line will over-ride the config file
|
# The command line will over-ride the config file
|
||||||
# except in the case of the tech name! This is because the tech name
|
# except in the case of the tech name! This is because the tech name
|
||||||
# is sometimes used to specify the config file itself (e.g. unit tests)
|
# is sometimes used to specify the config file itself (e.g. unit tests)
|
||||||
# Note that if we re-read a config file, nothing will get read again!
|
# 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 k not in OPTS.__dict__ or k == "tech_name":
|
||||||
OPTS.__dict__[k]=v
|
OPTS.__dict__[k] = v
|
||||||
|
OPTS.overridden[k] = True
|
||||||
|
|
||||||
# Massage the output path to be an absolute one
|
# Massage the output path to be an absolute one
|
||||||
if not OPTS.output_path.endswith('/'):
|
if not OPTS.output_path.endswith('/'):
|
||||||
|
|
@ -281,20 +318,20 @@ def read_config(config_file, is_unit_test=True):
|
||||||
debug.info(1, "Output saved in " + OPTS.output_path)
|
debug.info(1, "Output saved in " + OPTS.output_path)
|
||||||
|
|
||||||
# Remember if we are running unit tests to reduce output
|
# 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 we are only generating a netlist, we can't do DRC/LVS
|
||||||
if OPTS.netlist_only:
|
if OPTS.netlist_only:
|
||||||
OPTS.check_lvsdrc=False
|
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 == ""):
|
||||||
ports = ""
|
ports = ""
|
||||||
if OPTS.num_rw_ports>0:
|
if OPTS.num_rw_ports > 0:
|
||||||
ports += "{}rw_".format(OPTS.num_rw_ports)
|
ports += "{}rw_".format(OPTS.num_rw_ports)
|
||||||
if OPTS.num_w_ports>0:
|
if OPTS.num_w_ports > 0:
|
||||||
ports += "{}w_".format(OPTS.num_w_ports)
|
ports += "{}w_".format(OPTS.num_w_ports)
|
||||||
if OPTS.num_r_ports>0:
|
if OPTS.num_r_ports > 0:
|
||||||
ports += "{}r_".format(OPTS.num_r_ports)
|
ports += "{}r_".format(OPTS.num_r_ports)
|
||||||
OPTS.output_name = "sram_{0}b_{1}_{2}{3}".format(OPTS.word_size,
|
OPTS.output_name = "sram_{0}b_{1}_{2}{3}".format(OPTS.word_size,
|
||||||
OPTS.num_words,
|
OPTS.num_words,
|
||||||
|
|
@ -302,8 +339,6 @@ def read_config(config_file, is_unit_test=True):
|
||||||
OPTS.tech_name)
|
OPTS.tech_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def end_openram():
|
def end_openram():
|
||||||
""" Clean up openram for a proper exit """
|
""" Clean up openram for a proper exit """
|
||||||
cleanup_paths()
|
cleanup_paths()
|
||||||
|
|
@ -312,9 +347,7 @@ def end_openram():
|
||||||
import verify
|
import verify
|
||||||
verify.print_drc_stats()
|
verify.print_drc_stats()
|
||||||
verify.print_lvs_stats()
|
verify.print_lvs_stats()
|
||||||
verify.print_pex_stats()
|
verify.print_pex_stats()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_paths():
|
def cleanup_paths():
|
||||||
|
|
@ -323,54 +356,62 @@ def cleanup_paths():
|
||||||
"""
|
"""
|
||||||
global OPTS
|
global OPTS
|
||||||
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
|
||||||
elif os.path.exists(OPTS.openram_temp):
|
elif os.path.exists(OPTS.openram_temp):
|
||||||
debug.info(1,"Purging temp directory: {}".format(OPTS.openram_temp))
|
debug.info(1,
|
||||||
# This annoyingly means you have to re-cd into the directory each debug iteration
|
"Purging temp directory: {}".format(OPTS.openram_temp))
|
||||||
#shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
# This annoyingly means you have to re-cd into
|
||||||
|
# the directory each debug iteration
|
||||||
|
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||||
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
||||||
for i in contents:
|
for i in contents:
|
||||||
if os.path.isfile(i) or os.path.islink(i):
|
if os.path.isfile(i) or os.path.islink(i):
|
||||||
os.remove(i)
|
os.remove(i)
|
||||||
else:
|
else:
|
||||||
shutil.rmtree(i)
|
shutil.rmtree(i)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def setup_paths():
|
def setup_paths():
|
||||||
""" Set up the non-tech related paths. """
|
""" Set up the non-tech related paths. """
|
||||||
debug.info(2,"Setting up paths...")
|
debug.info(2, "Setting up paths...")
|
||||||
|
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
try:
|
try:
|
||||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||||
except:
|
except:
|
||||||
debug.error("$OPENRAM_HOME is not properly defined.",1)
|
debug.error("$OPENRAM_HOME is not properly defined.", 1)
|
||||||
debug.check(os.path.isdir(OPENRAM_HOME),"$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME))
|
debug.check(os.path.isdir(OPENRAM_HOME),
|
||||||
|
"$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME))
|
||||||
|
|
||||||
# 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
|
||||||
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
|
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
|
||||||
for subdir in subdirlist:
|
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))
|
||||||
sys.path.append("{0}".format(full_path))
|
if "__pycache__" not in full_path:
|
||||||
|
sys.path.append("{0}".format(full_path))
|
||||||
|
|
||||||
if not OPTS.openram_temp.endswith('/'):
|
if not OPTS.openram_temp.endswith('/'):
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def is_exe(fpath):
|
def is_exe(fpath):
|
||||||
""" Return true if the given is an executable file that exists. """
|
""" Return true if the given is an executable file that exists. """
|
||||||
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
||||||
|
|
||||||
|
|
||||||
def find_exe(check_exe):
|
def find_exe(check_exe):
|
||||||
""" Check if the binary exists in any path dir and return the full path. """
|
"""
|
||||||
|
Check if the binary exists in any path dir
|
||||||
|
and return the full path.
|
||||||
|
"""
|
||||||
# Check if the preferred spice option exists in the path
|
# Check if the preferred spice option exists in the path
|
||||||
for path in os.environ["PATH"].split(os.pathsep):
|
for path in os.environ["PATH"].split(os.pathsep):
|
||||||
exe = os.path.join(path, check_exe)
|
exe = os.path.join(path, check_exe)
|
||||||
|
|
@ -379,12 +420,14 @@ def find_exe(check_exe):
|
||||||
return exe
|
return exe
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def init_paths():
|
def init_paths():
|
||||||
""" Create the temp and output directory if it doesn't exist """
|
""" Create the temp and output directory if it doesn't exist """
|
||||||
|
|
||||||
# make the directory if it doesn't exist
|
# make the directory if it doesn't exist
|
||||||
try:
|
try:
|
||||||
debug.info(1,"Creating temp directory: {}".format(OPTS.openram_temp))
|
debug.info(1,
|
||||||
|
"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||||
os.makedirs(OPTS.openram_temp, 0o750)
|
os.makedirs(OPTS.openram_temp, 0o750)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 17: # errno.EEXIST
|
if e.errno == 17: # errno.EEXIST
|
||||||
|
|
@ -398,35 +441,48 @@ def init_paths():
|
||||||
if e.errno == 17: # errno.EEXIST
|
if e.errno == 17: # errno.EEXIST
|
||||||
os.chmod(OPTS.output_path, 0o750)
|
os.chmod(OPTS.output_path, 0o750)
|
||||||
except:
|
except:
|
||||||
debug.error("Unable to make output directory.",-1)
|
debug.error("Unable to make output directory.", -1)
|
||||||
|
|
||||||
|
|
||||||
def set_default_corner():
|
def set_default_corner():
|
||||||
""" Set the default corner. """
|
""" Set the default corner. """
|
||||||
|
|
||||||
|
import tech
|
||||||
# Set some default options now based on the technology...
|
# Set some default options now based on the technology...
|
||||||
if (OPTS.process_corners == ""):
|
if (OPTS.process_corners == ""):
|
||||||
OPTS.process_corners = tech.spice["fet_models"].keys()
|
if OPTS.nominal_corner_only:
|
||||||
|
OPTS.process_corners = ["TT"]
|
||||||
|
else:
|
||||||
|
OPTS.process_corners = tech.spice["fet_models"].keys()
|
||||||
if (OPTS.supply_voltages == ""):
|
if (OPTS.supply_voltages == ""):
|
||||||
OPTS.supply_voltages = tech.spice["supply_voltages"]
|
if OPTS.nominal_corner_only:
|
||||||
|
OPTS.supply_voltages = [tech.spice["supply_voltages"][1]]
|
||||||
|
else:
|
||||||
|
OPTS.supply_voltages = tech.spice["supply_voltages"]
|
||||||
if (OPTS.temperatures == ""):
|
if (OPTS.temperatures == ""):
|
||||||
OPTS.temperatures = tech.spice["temperatures"]
|
if OPTS.nominal_corner_only:
|
||||||
|
OPTS.temperatures = [tech.spice["temperatures"][1]]
|
||||||
|
else:
|
||||||
|
OPTS.temperatures = tech.spice["temperatures"]
|
||||||
|
|
||||||
|
|
||||||
def import_tech():
|
def import_tech():
|
||||||
""" Dynamically adds the tech directory to the path and imports it. """
|
""" Dynamically adds the tech directory to the path and imports it. """
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
debug.info(2,"Importing technology: " + OPTS.tech_name)
|
debug.info(2,
|
||||||
|
"Importing technology: " + OPTS.tech_name)
|
||||||
|
|
||||||
# environment variable should point to the technology dir
|
# environment variable should point to the technology dir
|
||||||
try:
|
try:
|
||||||
OPENRAM_TECH = os.path.abspath(os.environ.get("OPENRAM_TECH"))
|
OPENRAM_TECH = os.path.abspath(os.environ.get("OPENRAM_TECH"))
|
||||||
except:
|
except:
|
||||||
debug.error("$OPENRAM_TECH environment variable is not defined.",1)
|
debug.error("$OPENRAM_TECH environment variable is not defined.", 1)
|
||||||
|
|
||||||
# Add all of the paths
|
# Add all of the paths
|
||||||
for tech_path in OPENRAM_TECH.split(":"):
|
for tech_path in OPENRAM_TECH.split(":"):
|
||||||
debug.check(os.path.isdir(tech_path),"$OPENRAM_TECH does not exist: {0}".format(tech_path))
|
debug.check(os.path.isdir(tech_path),
|
||||||
|
"$OPENRAM_TECH does not exist: {0}".format(tech_path))
|
||||||
sys.path.append(tech_path)
|
sys.path.append(tech_path)
|
||||||
debug.info(1, "Adding technology path: {}".format(tech_path))
|
debug.info(1, "Adding technology path: {}".format(tech_path))
|
||||||
|
|
||||||
|
|
@ -434,11 +490,10 @@ def import_tech():
|
||||||
try:
|
try:
|
||||||
tech_mod = __import__(OPTS.tech_name)
|
tech_mod = __import__(OPTS.tech_name)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
debug.error("Nonexistent technology_setup_file: {0}.py".format(filename), -1)
|
debug.error("Nonexistent technology module: {0}".format(OPTS.tech_name), -1)
|
||||||
|
|
||||||
OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/"
|
OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/"
|
||||||
|
|
||||||
|
|
||||||
# Add the tech directory
|
# Add the tech directory
|
||||||
tech_path = OPTS.openram_tech
|
tech_path = OPTS.openram_tech
|
||||||
sys.path.append(tech_path)
|
sys.path.append(tech_path)
|
||||||
|
|
@ -447,61 +502,68 @@ def import_tech():
|
||||||
except ImportError:
|
except ImportError:
|
||||||
debug.error("Could not load tech module.", -1)
|
debug.error("Could not load tech module.", -1)
|
||||||
|
|
||||||
|
# Add custom modules of the technology to the path, if they exist
|
||||||
|
custom_mod_path = os.path.join(tech_path, "modules/")
|
||||||
|
if os.path.exists(custom_mod_path):
|
||||||
|
sys.path.append(custom_mod_path)
|
||||||
|
|
||||||
|
|
||||||
def print_time(name, now_time, last_time=None, indentation=2):
|
def print_time(name, now_time, last_time=None, indentation=2):
|
||||||
""" Print a statement about the time delta. """
|
""" Print a statement about the time delta. """
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
# Don't print during testing
|
# Don't print during testing
|
||||||
if not OPTS.is_unit_test or OPTS.debug_level>0:
|
if not OPTS.is_unit_test or OPTS.debug_level > 0:
|
||||||
if last_time:
|
if last_time:
|
||||||
time = str(round((now_time-last_time).total_seconds(),1)) + " seconds"
|
time = str(round((now_time-last_time).total_seconds(),1)) + " seconds"
|
||||||
else:
|
else:
|
||||||
time = now_time.strftime('%m/%d/%Y %H:%M:%S')
|
time = now_time.strftime('%m/%d/%Y %H:%M:%S')
|
||||||
debug.print_raw("{0} {1}: {2}".format("*"*indentation,name,time))
|
debug.print_raw("{0} {1}: {2}".format("*"*indentation, name, time))
|
||||||
|
|
||||||
|
|
||||||
def report_status():
|
def report_status():
|
||||||
""" Check for valid arguments and report the info about the SRAM being generated """
|
"""
|
||||||
|
Check for valid arguments and report the
|
||||||
|
info about the SRAM being generated
|
||||||
|
"""
|
||||||
global OPTS
|
global OPTS
|
||||||
|
|
||||||
# Check if all arguments are integers for bits, size, banks
|
# Check if all arguments are integers for bits, size, banks
|
||||||
if type(OPTS.word_size)!=int:
|
if type(OPTS.word_size) != int:
|
||||||
debug.error("{0} is not an integer in config file.".format(OPTS.word_size))
|
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.write_size) is not int and OPTS.write_size is not None:
|
if type(OPTS.write_size) is not int and OPTS.write_size is not None:
|
||||||
debug.error("{0} is not an integer in config file.".format(OPTS.write_size))
|
debug.error("{0} is not an integer in config file.".format(OPTS.write_size))
|
||||||
|
|
||||||
# If a write mask is specified by the user, the mask write size should be the same as
|
# If a write mask is specified by the user, the mask write size should be the same as
|
||||||
# the word size so that an entire word is written at once.
|
# the word size so that an entire word is written at once.
|
||||||
if OPTS.write_size is not None:
|
if OPTS.write_size is not None:
|
||||||
if (OPTS.word_size % OPTS.write_size != 0):
|
if (OPTS.word_size % OPTS.write_size != 0):
|
||||||
debug.error("Write size needs to be an integer multiple of word size.")
|
debug.error("Write size needs to be an integer multiple of word size.")
|
||||||
# If write size is more than half of the word size, then it doesn't need a write mask. It would be writing
|
# If write size is more than half of the word size,
|
||||||
|
# then it doesn't need a write mask. It would be writing
|
||||||
# the whole word.
|
# the whole word.
|
||||||
if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size/2):
|
if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size/2):
|
||||||
debug.error("Write size needs to be between 1 bit and {0} bits/2.".format(OPTS.word_size))
|
debug.error("Write size needs to be between 1 bit and {0} bits/2.".format(OPTS.word_size))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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.")
|
||||||
|
|
||||||
debug.print_raw("Technology: {0}".format(OPTS.tech_name))
|
debug.print_raw("Technology: {0}".format(OPTS.tech_name))
|
||||||
total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks
|
total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks
|
||||||
debug.print_raw("Total size: {} bits".format(total_size))
|
debug.print_raw("Total size: {} bits".format(total_size))
|
||||||
if total_size>=2**14:
|
if total_size >= 2**14:
|
||||||
debug.warning("Requesting such a large memory size ({0}) will have a large run-time. ".format(total_size) +
|
debug.warning("Requesting such a large memory size ({0}) will have a large run-time. ".format(total_size) +
|
||||||
"Consider using multiple smaller banks.")
|
"Consider using multiple smaller banks.")
|
||||||
debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
|
debug.print_raw("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.write_size != OPTS.word_size):
|
if (OPTS.write_size != OPTS.word_size):
|
||||||
debug.print_raw("Write size: {}".format(OPTS.write_size))
|
debug.print_raw("Write size: {}".format(OPTS.write_size))
|
||||||
debug.print_raw("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports,
|
debug.print_raw("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports,
|
||||||
OPTS.num_r_ports,
|
OPTS.num_r_ports,
|
||||||
OPTS.num_w_ports))
|
OPTS.num_w_ports))
|
||||||
|
|
||||||
if OPTS.netlist_only:
|
if OPTS.netlist_only:
|
||||||
debug.print_raw("Netlist only mode (no physical design is being done, netlist_only=False to disable).")
|
debug.print_raw("Netlist only mode (no physical design is being done, netlist_only=False to disable).")
|
||||||
|
|
@ -518,9 +580,9 @@ def report_status():
|
||||||
if OPTS.analytical_delay:
|
if OPTS.analytical_delay:
|
||||||
debug.print_raw("Characterization is disabled (using analytical delay models) (analytical_delay=False to simulate).")
|
debug.print_raw("Characterization is disabled (using analytical delay models) (analytical_delay=False to simulate).")
|
||||||
else:
|
else:
|
||||||
if OPTS.spice_name!="":
|
if OPTS.spice_name != "":
|
||||||
debug.print_raw("Performing simulation-based characterization with {}".format(OPTS.spice_name))
|
debug.print_raw("Performing simulation-based characterization with {}".format(OPTS.spice_name))
|
||||||
if OPTS.trim_netlist:
|
if OPTS.trim_netlist:
|
||||||
debug.print_raw("Trimming netlist to speed up characterization (trim_netlist=False to disable).")
|
debug.print_raw("Trimming netlist to speed up characterization (trim_netlist=False to disable).")
|
||||||
|
if OPTS.nominal_corner_only:
|
||||||
|
debug.print_raw("Only characterizing nominal corner.")
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -42,10 +42,12 @@ class bank_select(design.design):
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
self.route_instances()
|
self.route_instances()
|
||||||
|
|
||||||
|
self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width
|
||||||
|
self.width = max([x.rx() for x in self.inv_inst])
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
|
||||||
# Number of control lines in the bus
|
# Number of control lines in the bus
|
||||||
|
|
@ -61,19 +63,18 @@ class bank_select(design.design):
|
||||||
if (self.port == "rw") or (self.port == "r"):
|
if (self.port == "rw") or (self.port == "r"):
|
||||||
self.input_control_signals.append("s_en")
|
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]
|
||||||
|
|
||||||
self.add_pin_list(self.input_control_signals, "INPUT")
|
self.add_pin_list(self.input_control_signals, "INPUT")
|
||||||
self.add_pin("bank_sel")
|
self.add_pin("bank_sel")
|
||||||
self.add_pin_list(self.control_signals, "OUTPUT")
|
self.add_pin_list(self.control_signals, "OUTPUT")
|
||||||
self.add_pin("vdd","POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd","GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Create modules for later instantiation """
|
""" Create modules for later instantiation """
|
||||||
self.bitcell = factory.create(module_type="bitcell")
|
self.dff = factory.create(module_type="dff")
|
||||||
|
height = self.dff.height + drc("poly_to_active")
|
||||||
height = self.bitcell.height + drc("poly_to_active")
|
|
||||||
|
|
||||||
# 1x Inverter
|
# 1x Inverter
|
||||||
self.inv_sel = factory.create(module_type="pinv", height=height)
|
self.inv_sel = factory.create(module_type="pinv", height=height)
|
||||||
|
|
@ -94,20 +95,15 @@ class bank_select(design.design):
|
||||||
|
|
||||||
def calculate_module_offsets(self):
|
def calculate_module_offsets(self):
|
||||||
|
|
||||||
self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
self.xoffset_nand = self.inv4x.width + 3 * 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 + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||||
self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
|
self.xoffset_bank_sel_inv = 0
|
||||||
self.xoffset_bank_sel_inv = 0
|
|
||||||
self.xoffset_inputs = 0
|
self.xoffset_inputs = 0
|
||||||
|
|
||||||
self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height
|
self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height
|
||||||
# Include the M1 pitches for the supply rails and spacing
|
|
||||||
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
|
|
||||||
self.width = self.xoffset_inv + self.inv4x.width
|
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
|
|
||||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||||
mod=self.inv_sel)
|
mod=self.inv_sel)
|
||||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||||
|
|
||||||
|
|
@ -124,36 +120,36 @@ class bank_select(design.design):
|
||||||
# (writes occur on clk low)
|
# (writes occur on clk low)
|
||||||
if input_name in ("clk_buf"):
|
if input_name in ("clk_buf"):
|
||||||
|
|
||||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||||
mod=self.nor2))
|
mod=self.nor2))
|
||||||
self.connect_inst([input_name,
|
self.connect_inst([input_name,
|
||||||
"bank_sel_bar",
|
"bank_sel_bar",
|
||||||
gated_name+"_temp_bar",
|
gated_name + "_temp_bar",
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"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,
|
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||||
mod=self.inv4x_nor))
|
mod=self.inv4x_nor))
|
||||||
self.connect_inst([gated_name+"_temp_bar",
|
self.connect_inst([gated_name + "_temp_bar",
|
||||||
gated_name,
|
gated_name,
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"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,
|
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||||
mod=self.nand2))
|
mod=self.nand2))
|
||||||
self.connect_inst([input_name,
|
self.connect_inst([input_name,
|
||||||
"bank_sel",
|
"bank_sel",
|
||||||
gated_name+"_temp_bar",
|
gated_name + "_temp_bar",
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"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,
|
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||||
mod=self.inv4x))
|
mod=self.inv4x))
|
||||||
self.connect_inst([gated_name+"_temp_bar",
|
self.connect_inst([gated_name + "_temp_bar",
|
||||||
gated_name,
|
gated_name,
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"gnd"])
|
||||||
|
|
@ -176,9 +172,9 @@ class bank_select(design.design):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
y_offset = 0
|
y_offset = 0
|
||||||
else:
|
else:
|
||||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i-1)
|
y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1)
|
||||||
|
|
||||||
if i%2:
|
if i % 2:
|
||||||
y_offset += self.inv4x.height
|
y_offset += self.inv4x.height
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
else:
|
else:
|
||||||
|
|
@ -197,9 +193,8 @@ class bank_select(design.design):
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
# They all get inverters on the output
|
# They all get inverters on the output
|
||||||
inv_inst.place(offset=[self.xoffset_inv, y_offset],
|
inv_inst.place(offset=[logic_inst.rx(), y_offset],
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
|
|
||||||
def route_instances(self):
|
def route_instances(self):
|
||||||
|
|
||||||
|
|
@ -208,72 +203,72 @@ class bank_select(design.design):
|
||||||
xoffset_bank_sel = bank_sel_inv_pin.lx()
|
xoffset_bank_sel = bank_sel_inv_pin.lx()
|
||||||
bank_sel_line_pos = vector(xoffset_bank_sel, 0)
|
bank_sel_line_pos = vector(xoffset_bank_sel, 0)
|
||||||
bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint)
|
bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint)
|
||||||
self.add_path("metal2", [bank_sel_line_pos, bank_sel_line_end])
|
self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end])
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=bank_sel_inv_pin.lc())
|
offset=bank_sel_inv_pin.center())
|
||||||
|
|
||||||
# Route the pin to the left edge as well
|
# Route the pin to the left edge as well
|
||||||
bank_sel_pin_pos=vector(0, 0)
|
bank_sel_pin_pos=vector(0, 0)
|
||||||
bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y)
|
bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y)
|
||||||
self.add_layout_pin_segment_center(text="bank_sel",
|
self.add_layout_pin_segment_center(text="bank_sel",
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
start=bank_sel_pin_pos,
|
start=bank_sel_pin_pos,
|
||||||
end=bank_sel_pin_end)
|
end=bank_sel_pin_end)
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=bank_sel_pin_end,
|
offset=bank_sel_pin_end,
|
||||||
directions=("H","H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
# bank_sel_bar is vertical wire
|
# bank_sel_bar is vertical wire
|
||||||
bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z")
|
bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z")
|
||||||
xoffset_bank_sel_bar = bank_sel_bar_pin.rx()
|
xoffset_bank_sel_bar = bank_sel_bar_pin.rx()
|
||||||
self.add_label_pin(text="bank_sel_bar",
|
self.add_label_pin(text="bank_sel_bar",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=vector(xoffset_bank_sel_bar, 0),
|
offset=vector(xoffset_bank_sel_bar, 0),
|
||||||
height=self.inv4x.height)
|
height=self.inv4x.height)
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=bank_sel_bar_pin.rc())
|
offset=bank_sel_bar_pin.rc())
|
||||||
|
|
||||||
|
|
||||||
for i in range(self.num_control_lines):
|
for i in range(self.num_control_lines):
|
||||||
|
|
||||||
logic_inst = self.logic_inst[i]
|
logic_inst = self.logic_inst[i]
|
||||||
inv_inst = self.inv_inst[i]
|
inv_inst = self.inv_inst[i]
|
||||||
|
|
||||||
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"):
|
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
|
||||||
|
|
||||||
# Connect the logic output to inverter input
|
# Connect the logic output to inverter input
|
||||||
pre = logic_inst.get_pin("Z").lc()
|
out_pin = logic_inst.get_pin("Z")
|
||||||
out_position = logic_inst.get_pin("Z").rc() + vector(0.5*self.m1_width,0)
|
out_pos = out_pin.center()
|
||||||
in_position = inv_inst.get_pin("A").lc() + vector(0.5*self.m1_width,0)
|
in_pin = inv_inst.get_pin("A")
|
||||||
post = inv_inst.get_pin("A").rc()
|
in_pos = in_pin.center()
|
||||||
self.add_path("metal1", [pre, out_position, in_position, post])
|
mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y)
|
||||||
|
mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y)
|
||||||
|
self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos])
|
||||||
|
|
||||||
|
# Connect the logic B input to bank_sel / bank_sel_bar
|
||||||
# Connect the logic B input to bank_sel/bank_sel_bar
|
logic_pin = logic_inst.get_pin("B")
|
||||||
logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1m2.height,0)
|
logic_pos = logic_pin.center()
|
||||||
input_pos = vector(xoffset_bank_signal, logic_pos.y)
|
input_pos = vector(xoffset_bank_signal, logic_pos.y)
|
||||||
self.add_path("metal2",[logic_pos, input_pos])
|
self.add_path("m3", [logic_pos, input_pos])
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(self.m2_stack,
|
||||||
offset=logic_pos,
|
input_pos)
|
||||||
directions=("H","H"))
|
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||||
|
to_layer="m3",
|
||||||
|
offset=logic_pos)
|
||||||
|
|
||||||
|
|
||||||
# Connect the logic A input to the input pin
|
# Connect the logic A input to the input pin
|
||||||
logic_pos = logic_inst.get_pin("A").lc()
|
logic_pin = logic_inst.get_pin("A")
|
||||||
input_pos = vector(0,logic_pos.y)
|
logic_pos = logic_pin.center()
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
input_pos = vector(0, logic_pos.y)
|
||||||
offset=logic_pos,
|
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||||
directions=("H","H"))
|
to_layer="m3",
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
offset=logic_pos)
|
||||||
offset=logic_pos,
|
|
||||||
directions=("H","H"))
|
|
||||||
self.add_layout_pin_segment_center(text=input_name,
|
self.add_layout_pin_segment_center(text=input_name,
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
start=input_pos,
|
start=input_pos,
|
||||||
end=logic_pos)
|
end=logic_pos)
|
||||||
|
|
||||||
|
|
@ -285,7 +280,6 @@ class bank_select(design.design):
|
||||||
width=inv_inst.rx() - out_pin.lx(),
|
width=inv_inst.rx() - out_pin.lx(),
|
||||||
height=out_pin.height())
|
height=out_pin.height())
|
||||||
|
|
||||||
|
|
||||||
# Find the x offsets for where the vias/pins should be placed
|
# Find the x offsets for where the vias/pins should be placed
|
||||||
a_xoffset = self.logic_inst[0].lx()
|
a_xoffset = self.logic_inst[0].lx()
|
||||||
b_xoffset = self.inv_inst[0].lx()
|
b_xoffset = self.inv_inst[0].lx()
|
||||||
|
|
@ -293,35 +287,35 @@ class bank_select(design.design):
|
||||||
# Route both supplies
|
# Route both supplies
|
||||||
for n in ["vdd", "gnd"]:
|
for n in ["vdd", "gnd"]:
|
||||||
supply_pin = self.inv_inst[num].get_pin(n)
|
supply_pin = self.inv_inst[num].get_pin(n)
|
||||||
supply_offset = supply_pin.ll().scale(0,1)
|
supply_offset = supply_pin.ll().scale(0, 1)
|
||||||
self.add_rect(layer="metal1",
|
self.add_rect(layer="m1",
|
||||||
offset=supply_offset,
|
offset=supply_offset,
|
||||||
width=self.width)
|
width=self.width)
|
||||||
|
|
||||||
# Add pins in two locations
|
# Add pins in two locations
|
||||||
for xoffset in [a_xoffset, b_xoffset]:
|
for xoffset in [a_xoffset, b_xoffset]:
|
||||||
pin_pos = vector(xoffset, supply_pin.cy())
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=pin_pos,
|
offset=pin_pos,
|
||||||
directions=("H","H"))
|
directions=("H", "H"))
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=pin_pos,
|
offset=pin_pos,
|
||||||
directions=("H","H"))
|
directions=("H", "H"))
|
||||||
self.add_layout_pin_rect_center(text=n,
|
self.add_layout_pin_rect_center(text=n,
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
offset=pin_pos)
|
offset=pin_pos)
|
||||||
|
|
||||||
# Add vdd/gnd supply rails
|
# Add vdd/gnd supply rails
|
||||||
gnd_pin = inv_inst.get_pin("gnd")
|
gnd_pin = self.inv_inst[num].get_pin("gnd")
|
||||||
left_gnd_pos = vector(0, gnd_pin.cy())
|
left_gnd_pos = vector(0, gnd_pin.cy())
|
||||||
self.add_layout_pin_segment_center(text="gnd",
|
self.add_layout_pin_segment_center(text="gnd",
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
start=left_gnd_pos,
|
start=left_gnd_pos,
|
||||||
end=gnd_pin.rc())
|
end=gnd_pin.rc())
|
||||||
|
|
||||||
vdd_pin = inv_inst.get_pin("vdd")
|
vdd_pin = self.inv_inst[num].get_pin("vdd")
|
||||||
left_vdd_pos = vector(0, vdd_pin.cy())
|
left_vdd_pos = vector(0, vdd_pin.cy())
|
||||||
self.add_layout_pin_segment_center(text="vdd",
|
self.add_layout_pin_segment_center(text="vdd",
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
start=left_vdd_pos,
|
start=left_vdd_pos,
|
||||||
end=vdd_pin.rc())
|
end=vdd_pin.rc())
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,20 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import debug
|
from bitcell_base_array import bitcell_base_array
|
||||||
import design
|
|
||||||
from tech import drc, spice
|
from tech import drc, spice
|
||||||
from vector import vector
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
import logical_effort
|
|
||||||
|
|
||||||
class bitcell_array(design.design):
|
|
||||||
|
class bitcell_array(bitcell_base_array):
|
||||||
"""
|
"""
|
||||||
Creates a rows x cols array of memory cells. Assumes bit-lines
|
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):
|
def __init__(self, cols, rows, name, column_offset=0):
|
||||||
design.design.__init__(self, name)
|
super().__init__(cols, rows, name, column_offset)
|
||||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
|
||||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
|
||||||
|
|
||||||
self.column_size = cols
|
|
||||||
self.row_size = rows
|
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
|
|
@ -33,8 +26,7 @@ class bitcell_array(design.design):
|
||||||
|
|
||||||
# We don't offset this because we need to align
|
# We don't offset this because we need to align
|
||||||
# the replica bitcell in the control logic
|
# the replica bitcell in the control logic
|
||||||
#self.offset_all_coordinates()
|
# self.offset_all_coordinates()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
""" Create and connect the netlist """
|
""" Create and connect the netlist """
|
||||||
|
|
@ -44,27 +36,7 @@ class bitcell_array(design.design):
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
||||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
self.place_array("bit_r{0}_c{1}")
|
||||||
self.height = self.row_size*self.cell.height
|
|
||||||
self.width = self.column_size*self.cell.width
|
|
||||||
|
|
||||||
xoffset = 0.0
|
|
||||||
for col in range(self.column_size):
|
|
||||||
yoffset = 0.0
|
|
||||||
for row in range(self.row_size):
|
|
||||||
name = "bit_r{0}_c{1}".format(row, col)
|
|
||||||
|
|
||||||
if row % 2:
|
|
||||||
tempy = yoffset + self.cell.height
|
|
||||||
dir_key = "MX"
|
|
||||||
else:
|
|
||||||
tempy = yoffset
|
|
||||||
dir_key = ""
|
|
||||||
|
|
||||||
self.cell_inst[row,col].place(offset=[xoffset, tempy],
|
|
||||||
mirror=dir_key)
|
|
||||||
yoffset += self.cell.height
|
|
||||||
xoffset += self.cell.width
|
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
|
|
@ -72,98 +44,35 @@ class bitcell_array(design.design):
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
row_list = self.cell.get_all_wl_names()
|
|
||||||
column_list = self.cell.get_all_bitline_names()
|
|
||||||
for col in range(self.column_size):
|
|
||||||
for cell_column in column_list:
|
|
||||||
self.add_pin(cell_column+"_{0}".format(col), "INOUT")
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
|
||||||
self.add_pin(cell_row+"_{0}".format(row), "INPUT")
|
|
||||||
self.add_pin("vdd", "POWER")
|
|
||||||
self.add_pin("gnd", "GROUND")
|
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add the modules used in this design """
|
""" Add the modules used in this design """
|
||||||
self.cell = factory.create(module_type="bitcell")
|
self.cell = factory.create(module_type="bitcell")
|
||||||
self.add_mod(self.cell)
|
self.add_mod(self.cell)
|
||||||
|
|
||||||
def get_bitcell_pins(self, col, row):
|
|
||||||
""" Creates a list of connections in the bitcell,
|
|
||||||
indexed by column and row, for instance use in bitcell_array """
|
|
||||||
|
|
||||||
bitcell_pins = []
|
|
||||||
|
|
||||||
pin_names = self.cell.get_all_bitline_names()
|
|
||||||
for pin in pin_names:
|
|
||||||
bitcell_pins.append(pin+"_{0}".format(col))
|
|
||||||
pin_names = self.cell.get_all_wl_names()
|
|
||||||
for pin in pin_names:
|
|
||||||
bitcell_pins.append(pin+"_{0}".format(row))
|
|
||||||
bitcell_pins.append("vdd")
|
|
||||||
bitcell_pins.append("gnd")
|
|
||||||
|
|
||||||
return bitcell_pins
|
|
||||||
|
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
""" Create the module instances used in this design """
|
""" Create the module instances used in this design """
|
||||||
self.cell_inst = {}
|
self.cell_inst = {}
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
name = "bit_r{0}_c{1}".format(row, col)
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
self.cell_inst[row,col]=self.add_inst(name=name,
|
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||||
mod=self.cell)
|
mod=self.cell)
|
||||||
self.connect_inst(self.get_bitcell_pins(col, row))
|
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||||
|
|
||||||
def add_layout_pins(self):
|
|
||||||
""" Add the layout pins """
|
|
||||||
|
|
||||||
row_list = self.cell.get_all_wl_names()
|
|
||||||
column_list = self.cell.get_all_bitline_names()
|
|
||||||
|
|
||||||
for col in range(self.column_size):
|
|
||||||
for cell_column in column_list:
|
|
||||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
|
||||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
|
||||||
layer=bl_pin.layer,
|
|
||||||
offset=bl_pin.ll().scale(1,0),
|
|
||||||
width=bl_pin.width(),
|
|
||||||
height=self.height)
|
|
||||||
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
|
||||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
|
||||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
|
||||||
layer=wl_pin.layer,
|
|
||||||
offset=wl_pin.ll().scale(0,1),
|
|
||||||
width=self.width,
|
|
||||||
height=wl_pin.height())
|
|
||||||
|
|
||||||
# For every second row and column, add a via for gnd and vdd
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for col in range(self.column_size):
|
|
||||||
inst = self.cell_inst[row,col]
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
|
||||||
for pin in inst.get_pins(pin_name):
|
|
||||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
|
||||||
|
|
||||||
def analytical_power(self, corner, load):
|
def analytical_power(self, corner, load):
|
||||||
"""Power of Bitcell array and bitline in nW."""
|
"""Power of Bitcell array and bitline in nW."""
|
||||||
from tech import drc, parameter
|
|
||||||
|
|
||||||
# Dynamic Power from Bitline
|
# Dynamic Power from Bitline
|
||||||
bl_wire = self.gen_bl_wire()
|
bl_wire = self.gen_bl_wire()
|
||||||
cell_load = 2 * bl_wire.return_input_cap()
|
cell_load = 2 * bl_wire.return_input_cap()
|
||||||
bl_swing = OPTS.rbl_delay_percentage
|
bl_swing = OPTS.rbl_delay_percentage
|
||||||
freq = spice["default_event_frequency"]
|
freq = spice["default_event_frequency"]
|
||||||
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
||||||
|
|
||||||
#Calculate the bitcell power which currently only includes leakage
|
# Calculate the bitcell power which currently only includes leakage
|
||||||
cell_power = self.cell.analytical_power(corner, load)
|
cell_power = self.cell.analytical_power(corner, load)
|
||||||
|
|
||||||
#Leakage power grows with entire array and bitlines.
|
# Leakage power grows with entire array and bitlines.
|
||||||
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
||||||
cell_power.leakage * self.column_size * self.row_size)
|
cell_power.leakage * self.column_size * self.row_size)
|
||||||
return total_power
|
return total_power
|
||||||
|
|
@ -173,8 +82,8 @@ class bitcell_array(design.design):
|
||||||
width = 0
|
width = 0
|
||||||
else:
|
else:
|
||||||
width = self.width
|
width = self.width
|
||||||
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1"))
|
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1"))
|
||||||
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
wl_wire.wire_c = 2 * spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||||
return wl_wire
|
return wl_wire
|
||||||
|
|
||||||
def gen_bl_wire(self):
|
def gen_bl_wire(self):
|
||||||
|
|
@ -183,26 +92,26 @@ class bitcell_array(design.design):
|
||||||
else:
|
else:
|
||||||
height = self.height
|
height = self.height
|
||||||
bl_pos = 0
|
bl_pos = 0
|
||||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
|
||||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||||
return bl_wire
|
return bl_wire
|
||||||
|
|
||||||
def get_wordline_cin(self):
|
def get_wordline_cin(self):
|
||||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||||
total_cin = bitcell_wl_cin * self.column_size
|
total_cin = bitcell_wl_cin * self.column_size
|
||||||
return total_cin
|
return total_cin
|
||||||
|
|
||||||
def graph_exclude_bits(self, targ_row, targ_col):
|
def graph_exclude_bits(self, targ_row, targ_col):
|
||||||
"""Excludes bits in column from being added to graph except target"""
|
"""Excludes bits in column from being added to graph except target"""
|
||||||
#Function is not robust with column mux configurations
|
# Function is not robust with column mux configurations
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
if row == targ_row and col == targ_col:
|
if row == targ_row and col == targ_col:
|
||||||
continue
|
continue
|
||||||
self.graph_inst_exclude.add(self.cell_inst[row,col])
|
self.graph_inst_exclude.add(self.cell_inst[row, col])
|
||||||
|
|
||||||
def get_cell_name(self, inst_name, row, col):
|
def get_cell_name(self, inst_name, row, col):
|
||||||
"""Gets the spice name of the target bitcell."""
|
"""Gets the spice name of the target bitcell."""
|
||||||
return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col]
|
return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
import design
|
||||||
|
from tech import cell_properties
|
||||||
|
|
||||||
|
|
||||||
|
class bitcell_base_array(design.design):
|
||||||
|
"""
|
||||||
|
Abstract base class for bitcell-arrays -- bitcell, dummy
|
||||||
|
"""
|
||||||
|
def __init__(self, cols, rows, name, column_offset):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
||||||
|
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
||||||
|
|
||||||
|
self.column_size = cols
|
||||||
|
self.row_size = rows
|
||||||
|
self.column_offset = column_offset
|
||||||
|
|
||||||
|
def get_all_bitline_names(self):
|
||||||
|
|
||||||
|
res = list()
|
||||||
|
bitline_names = self.cell.get_all_bitline_names()
|
||||||
|
|
||||||
|
# We have to keep the order of self.pins, otherwise we connect
|
||||||
|
# it wrong in the spice netlist
|
||||||
|
for pin in self.pins:
|
||||||
|
for bl_name in bitline_names:
|
||||||
|
if bl_name in pin:
|
||||||
|
res.append(pin)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def get_all_wordline_names(self):
|
||||||
|
|
||||||
|
res = list()
|
||||||
|
wordline_names = self.cell.get_all_wl_names()
|
||||||
|
|
||||||
|
# We have to keep the order of self.pins, otherwise we connect
|
||||||
|
# it wrong in the spice netlist
|
||||||
|
for pin in self.pins:
|
||||||
|
for wl_name in wordline_names:
|
||||||
|
if wl_name in pin:
|
||||||
|
res.append(pin)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
row_list = self.cell.get_all_wl_names()
|
||||||
|
column_list = self.cell.get_all_bitline_names()
|
||||||
|
for col in range(self.column_size):
|
||||||
|
for cell_column in column_list:
|
||||||
|
self.add_pin(cell_column+"_{0}".format(col), "INOUT")
|
||||||
|
for row in range(self.row_size):
|
||||||
|
for cell_row in row_list:
|
||||||
|
self.add_pin(cell_row+"_{0}".format(row), "INPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
def get_bitcell_pins(self, col, row):
|
||||||
|
""" Creates a list of connections in the bitcell,
|
||||||
|
indexed by column and row, for instance use in bitcell_array """
|
||||||
|
|
||||||
|
bitcell_pins = []
|
||||||
|
|
||||||
|
pin_names = self.cell.get_all_bitline_names()
|
||||||
|
for pin in pin_names:
|
||||||
|
bitcell_pins.append(pin + "_{0}".format(col))
|
||||||
|
pin_names = self.cell.get_all_wl_names()
|
||||||
|
for pin in pin_names:
|
||||||
|
bitcell_pins.append(pin + "_{0}".format(row))
|
||||||
|
bitcell_pins.append("vdd")
|
||||||
|
bitcell_pins.append("gnd")
|
||||||
|
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
""" Add the layout pins """
|
||||||
|
|
||||||
|
row_list = self.cell.get_all_wl_names()
|
||||||
|
column_list = self.cell.get_all_bitline_names()
|
||||||
|
|
||||||
|
for col in range(self.column_size):
|
||||||
|
for cell_column in column_list:
|
||||||
|
bl_pin = self.cell_inst[0, col].get_pin(cell_column)
|
||||||
|
self.add_layout_pin(text=cell_column + "_{0}".format(col),
|
||||||
|
layer=bl_pin.layer,
|
||||||
|
offset=bl_pin.ll().scale(1, 0),
|
||||||
|
width=bl_pin.width(),
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
for row in range(self.row_size):
|
||||||
|
for cell_row in row_list:
|
||||||
|
wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
|
||||||
|
self.add_layout_pin(text=cell_row + "_{0}".format(row),
|
||||||
|
layer=wl_pin.layer,
|
||||||
|
offset=wl_pin.ll().scale(0, 1),
|
||||||
|
width=self.width,
|
||||||
|
height=wl_pin.height())
|
||||||
|
|
||||||
|
# Copy a vdd/gnd layout pin from every cell
|
||||||
|
for row in range(self.row_size):
|
||||||
|
for col in range(self.column_size):
|
||||||
|
inst = self.cell_inst[row, col]
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
self.copy_layout_pin(inst, pin_name)
|
||||||
|
|
||||||
|
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||||
|
tempx = xoffset
|
||||||
|
dir_y = False
|
||||||
|
# If we mirror the current cell on the y axis adjust the x position
|
||||||
|
if cell_properties.bitcell.mirror.y and (col + col_offset) % 2:
|
||||||
|
tempx = xoffset + self.cell.width
|
||||||
|
dir_y = True
|
||||||
|
return (tempx, dir_y)
|
||||||
|
|
||||||
|
def _adjust_y_offset(self, yoffset, row, row_offset):
|
||||||
|
tempy = yoffset
|
||||||
|
dir_x = False
|
||||||
|
# If we mirror the current cell on the x axis adjust the y position
|
||||||
|
if cell_properties.bitcell.mirror.x and (row + row_offset) % 2:
|
||||||
|
tempy = yoffset + self.cell.height
|
||||||
|
dir_x = True
|
||||||
|
return (tempy, dir_x)
|
||||||
|
|
||||||
|
def place_array(self, name_template, row_offset=0):
|
||||||
|
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||||
|
self.height = self.row_size * self.cell.height
|
||||||
|
self.width = self.column_size * self.cell.width
|
||||||
|
|
||||||
|
xoffset = 0.0
|
||||||
|
for col in range(self.column_size):
|
||||||
|
yoffset = 0.0
|
||||||
|
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
||||||
|
|
||||||
|
for row in range(self.row_size):
|
||||||
|
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
|
||||||
|
|
||||||
|
if dir_x and dir_y:
|
||||||
|
dir_key = "XY"
|
||||||
|
elif dir_x:
|
||||||
|
dir_key = "MX"
|
||||||
|
elif dir_y:
|
||||||
|
dir_key = "MY"
|
||||||
|
else:
|
||||||
|
dir_key = ""
|
||||||
|
|
||||||
|
self.cell_inst[row, col].place(offset=[tempx, tempy],
|
||||||
|
mirror=dir_key)
|
||||||
|
yoffset += self.cell.height
|
||||||
|
xoffset += self.cell.width
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
from bitcell_base_array import bitcell_base_array
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
from tech import cell_properties
|
||||||
|
|
||||||
|
|
||||||
|
class col_cap_array(bitcell_base_array):
|
||||||
|
"""
|
||||||
|
Generate a dummy row/column for the replica array.
|
||||||
|
"""
|
||||||
|
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
|
||||||
|
super().__init__(cols, rows, name, column_offset)
|
||||||
|
self.mirror = mirror
|
||||||
|
|
||||||
|
self.no_instances = True
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
""" Create and connect the netlist """
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_instances()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
|
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Add the modules used in this design """
|
||||||
|
self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell))
|
||||||
|
self.add_mod(self.dummy_cell)
|
||||||
|
|
||||||
|
self.cell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
|
def create_instances(self):
|
||||||
|
""" Create the module instances used in this design """
|
||||||
|
self.cell_inst = {}
|
||||||
|
for col in range(self.column_size):
|
||||||
|
for row in range(self.row_size):
|
||||||
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
|
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||||
|
mod=self.dummy_cell)
|
||||||
|
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||||
|
|
||||||
|
def get_bitcell_pins(self, col, row):
|
||||||
|
"""
|
||||||
|
Creates a list of connections in the bitcell,
|
||||||
|
indexed by column and row, for instance use in bitcell_array
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_name = cell_properties.bitcell.cell_1rw1r.pin
|
||||||
|
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
|
||||||
|
"{0}_{1}".format(pin_name.br0, col),
|
||||||
|
"{0}_{1}".format(pin_name.bl1, col),
|
||||||
|
"{0}_{1}".format(pin_name.br1, col),
|
||||||
|
"vdd"]
|
||||||
|
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
""" Add the layout pins """
|
||||||
|
|
||||||
|
column_list = self.cell.get_all_bitline_names()
|
||||||
|
|
||||||
|
for col in range(self.column_size):
|
||||||
|
for cell_column in column_list:
|
||||||
|
bl_pin = self.cell_inst[0, col].get_pin(cell_column)
|
||||||
|
self.add_layout_pin(text=cell_column + "_{0}".format(col),
|
||||||
|
layer=bl_pin.layer,
|
||||||
|
offset=bl_pin.ll().scale(1, 0),
|
||||||
|
width=bl_pin.width(),
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
# Add vdd/gnd via stacks
|
||||||
|
for row in range(self.row_size):
|
||||||
|
for col in range(self.column_size):
|
||||||
|
inst = self.cell_inst[row, col]
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
for pin in inst.get_pins(pin_name):
|
||||||
|
self.add_power_pin(name=pin.name,
|
||||||
|
loc=pin.center(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,12 +7,11 @@
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc
|
|
||||||
from contact import contact
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
||||||
|
|
||||||
class delay_chain(design.design):
|
class delay_chain(design.design):
|
||||||
"""
|
"""
|
||||||
Generate a delay chain with the given number of stages and fanout.
|
Generate a delay chain with the given number of stages and fanout.
|
||||||
|
|
@ -28,7 +27,7 @@ class delay_chain(design.design):
|
||||||
|
|
||||||
# Two fanouts are needed so that we can route the vdd/gnd connections
|
# Two fanouts are needed so that we can route the vdd/gnd connections
|
||||||
for f in fanout_list:
|
for f in fanout_list:
|
||||||
debug.check(f>=2,"Must have >=2 fanouts for each stage.")
|
debug.check(f>=2, "Must have >=2 fanouts for each stage.")
|
||||||
|
|
||||||
# number of inverters including any fanout loads.
|
# number of inverters including any fanout loads.
|
||||||
self.fanout_list = fanout_list
|
self.fanout_list = fanout_list
|
||||||
|
|
@ -36,7 +35,6 @@ class delay_chain(design.design):
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
|
|
@ -45,12 +43,13 @@ class delay_chain(design.design):
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
# Each stage is a a row
|
# Each stage is a a row
|
||||||
self.height = len(self.fanout_list)*self.inv.height
|
self.height = len(self.fanout_list) * self.inv.height
|
||||||
# The width is determined by the largest fanout plus the driver
|
# The width is determined by the largest fanout plus the driver
|
||||||
self.width = (max(self.fanout_list)+1) * self.inv.width
|
self.width = (max(self.fanout_list) + 1) * self.inv.width
|
||||||
|
|
||||||
self.place_inverters()
|
self.place_inverters()
|
||||||
self.route_inverters()
|
self.route_inverters()
|
||||||
|
self.route_supplies()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
@ -63,15 +62,14 @@ class delay_chain(design.design):
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.inv = factory.create(module_type="pinv", route_output=False)
|
self.inv = factory.create(module_type="pinv")
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
def create_inverters(self):
|
def create_inverters(self):
|
||||||
""" Create the inverters and connect them based on the stage list """
|
""" Create the inverters and connect them based on the stage list """
|
||||||
self.driver_inst_list = []
|
self.driver_inst_list = []
|
||||||
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):
|
||||||
# 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)
|
||||||
|
|
@ -79,40 +77,37 @@ class delay_chain(design.design):
|
||||||
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"
|
||||||
else:
|
else:
|
||||||
stageout_name = "dout_{}".format(stage_num+1)
|
stageout_name = "dout_{}".format(stage_num + 1)
|
||||||
if stage_num == 0:
|
if stage_num == 0:
|
||||||
stagein_name = "in"
|
stagein_name = "in"
|
||||||
else:
|
else:
|
||||||
stagein_name = "dout_{}".format(stage_num)
|
stagein_name = "dout_{}".format(stage_num)
|
||||||
self.connect_inst([stagein_name, stageout_name, "vdd", "gnd"])
|
self.connect_inst([stagein_name, stageout_name, "vdd", "gnd"])
|
||||||
|
|
||||||
# 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):
|
||||||
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)
|
||||||
# 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"])
|
||||||
|
|
||||||
# Keep track of all the loads to connect their inputs as a load
|
# Keep track of all the loads to connect their inputs as a load
|
||||||
self.load_inst_map[cur_driver].append(cur_load)
|
self.load_inst_map[cur_driver].append(cur_load)
|
||||||
else:
|
|
||||||
# Keep track of the last one so we can add the the wire later
|
|
||||||
self.rightest_load_inst[cur_driver]=cur_load
|
|
||||||
|
|
||||||
def place_inverters(self):
|
def place_inverters(self):
|
||||||
""" Place the inverters and connect them based on the stage list """
|
""" Place the inverters and connect them based on the stage list """
|
||||||
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list):
|
||||||
if stage_num % 2:
|
if stage_num % 2:
|
||||||
inv_mirror = "MX"
|
inv_mirror = "MX"
|
||||||
inv_offset = vector(0, (stage_num+1)* self.inv.height)
|
inv_offset = vector(0, (stage_num + 1) * self.inv.height)
|
||||||
else:
|
else:
|
||||||
inv_mirror = "R0"
|
inv_mirror = "R0"
|
||||||
inv_offset = vector(0, stage_num * self.inv.height)
|
inv_offset = vector(0, stage_num * self.inv.height)
|
||||||
|
|
||||||
# Add the inverter
|
# Add the inverter
|
||||||
cur_driver=self.driver_inst_list[stage_num]
|
cur_driver=self.driver_inst_list[stage_num]
|
||||||
|
|
@ -122,21 +117,20 @@ class delay_chain(design.design):
|
||||||
# Now add the dummy loads to the right
|
# Now add the dummy loads to the right
|
||||||
load_list = self.load_inst_map[cur_driver]
|
load_list = 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)
|
inv_offset += vector(self.inv.width, 0)
|
||||||
load_list[i].place(offset=inv_offset,
|
load_list[i].place(offset=inv_offset,
|
||||||
mirror=inv_mirror)
|
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. """
|
||||||
pin1_pos = pin1.center()
|
pin1_pos = pin1.center()
|
||||||
pin2_pos = pin2.center()
|
pin2_pos = pin2.center()
|
||||||
if pin1_pos.y == pin2_pos.y:
|
if pin1_pos.y == pin2_pos.y:
|
||||||
self.add_path("metal2", [pin1_pos, pin2_pos])
|
self.add_path("m2", [pin1_pos, pin2_pos])
|
||||||
else:
|
else:
|
||||||
mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y))
|
mid_point = vector(pin2_pos.x, 0.5 * (pin1_pos.y + pin2_pos.y))
|
||||||
# Written this way to guarantee it goes right first if we are switching rows
|
# Written this way to guarantee it goes right first if we are switching rows
|
||||||
self.add_path("metal2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos])
|
self.add_path("m2", [pin1_pos, vector(pin1_pos.x, mid_point.y), mid_point, vector(mid_point.x, pin2_pos.y), pin2_pos])
|
||||||
|
|
||||||
def route_inverters(self):
|
def route_inverters(self):
|
||||||
""" Add metal routing for each of the fanout stages """
|
""" Add metal routing for each of the fanout stages """
|
||||||
|
|
@ -145,100 +139,93 @@ class delay_chain(design.design):
|
||||||
inv = self.driver_inst_list[i]
|
inv = self.driver_inst_list[i]
|
||||||
for load in self.load_inst_map[inv]:
|
for load in self.load_inst_map[inv]:
|
||||||
# Drop a via on each A pin
|
# Drop a via on each A pin
|
||||||
a_pin = load.get_pin("A")
|
a_pin = load.get_pin("A")
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||||
offset=a_pin.center())
|
to_layer="m3",
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
offset=a_pin.center())
|
||||||
offset=a_pin.center())
|
|
||||||
|
|
||||||
# Route an M3 horizontal wire to the furthest
|
# Route an M3 horizontal wire to the furthest
|
||||||
z_pin = inv.get_pin("Z")
|
z_pin = inv.get_pin("Z")
|
||||||
a_pin = inv.get_pin("A")
|
a_pin = inv.get_pin("A")
|
||||||
a_max = self.rightest_load_inst[inv].get_pin("A")
|
a_max = self.load_inst_map[inv][-1].get_pin("A")
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||||
offset=a_pin.center())
|
to_layer="m2",
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
offset=a_pin.center())
|
||||||
offset=z_pin.center())
|
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
to_layer="m3",
|
||||||
offset=z_pin.center())
|
offset=z_pin.center())
|
||||||
self.add_path("metal3",[z_pin.center(), a_max.center()])
|
self.add_path("m3", [z_pin.center(), a_max.center()])
|
||||||
|
|
||||||
|
|
||||||
# Route Z to the A of the next stage
|
# Route Z to the A of the next stage
|
||||||
if i+1 < len(self.driver_inst_list):
|
if i + 1 < len(self.driver_inst_list):
|
||||||
z_pin = inv.get_pin("Z")
|
z_pin = inv.get_pin("Z")
|
||||||
next_inv = self.driver_inst_list[i+1]
|
next_inv = self.driver_inst_list[i + 1]
|
||||||
next_a_pin = next_inv.get_pin("A")
|
next_a_pin = next_inv.get_pin("A")
|
||||||
y_mid = (z_pin.cy() + next_a_pin.cy())/2
|
y_mid = (z_pin.cy() + next_a_pin.cy()) / 2
|
||||||
mid1_point = vector(z_pin.cx(), y_mid)
|
mid1_point = vector(z_pin.cx(), y_mid)
|
||||||
mid2_point = vector(next_a_pin.cx(), y_mid)
|
mid2_point = vector(next_a_pin.cx(), y_mid)
|
||||||
self.add_path("metal2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()])
|
self.add_path("m2", [z_pin.center(), mid1_point, mid2_point, next_a_pin.center()])
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
|
||||||
""" Add vdd and gnd rails and the input/output. Connect the gnd rails internally on
|
|
||||||
the top end with no input/output to obstruct. """
|
|
||||||
|
|
||||||
|
def route_supplies(self):
|
||||||
# Add power and ground to all the cells except:
|
# Add power and ground to all the cells except:
|
||||||
# the fanout driver, the right-most load
|
# the fanout driver, the right-most load
|
||||||
# The routing to connect the loads is over the first and last cells
|
# The routing to connect the loads is over the first and last cells
|
||||||
# We have an even number of drivers and must only do every other
|
# We have an even number of drivers and must only do every other
|
||||||
# supply rail
|
# supply rail
|
||||||
for i in range(0,len(self.driver_inst_list),2):
|
|
||||||
inv = self.driver_inst_list[i]
|
|
||||||
for load in self.load_inst_map[inv]:
|
|
||||||
if load==self.rightest_load_inst[inv]:
|
|
||||||
continue
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
|
||||||
pin = load.get_pin(pin_name)
|
|
||||||
self.add_power_pin(pin_name, pin.rc())
|
|
||||||
else:
|
|
||||||
# We have an even number of rows, so need to get the last gnd rail
|
|
||||||
inv = self.driver_inst_list[-1]
|
|
||||||
for load in self.load_inst_map[inv]:
|
|
||||||
if load==self.rightest_load_inst[inv]:
|
|
||||||
continue
|
|
||||||
pin_name = "gnd"
|
|
||||||
pin = load.get_pin(pin_name)
|
|
||||||
self.add_power_pin(pin_name, pin.rc())
|
|
||||||
|
|
||||||
|
for inst in self.driver_inst_list:
|
||||||
|
load_list = self.load_inst_map[inst]
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
pin = load_list[0].get_pin(pin_name)
|
||||||
|
self.add_power_pin(pin_name,
|
||||||
|
pin.rc() - vector(self.m1_pitch, 0),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
pin = load_list[-2].get_pin(pin_name)
|
||||||
|
self.add_power_pin(pin_name,
|
||||||
|
pin.rc() - vector(self.m1_pitch, 0),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
|
||||||
# input is A pin of first inverter
|
# input is A pin of first inverter
|
||||||
a_pin = self.driver_inst_list[0].get_pin("A")
|
a_pin = self.driver_inst_list[0].get_pin("A")
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||||
offset=a_pin.center())
|
to_layer="m2",
|
||||||
|
offset=a_pin.center())
|
||||||
self.add_layout_pin(text="in",
|
self.add_layout_pin(text="in",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=a_pin.ll().scale(1,0),
|
offset=a_pin.ll().scale(1, 0),
|
||||||
height=a_pin.cy())
|
height=a_pin.cy())
|
||||||
|
|
||||||
|
|
||||||
# output is A pin of last load inverter
|
# output is A pin of last load inverter
|
||||||
last_driver_inst = self.driver_inst_list[-1]
|
last_driver_inst = self.driver_inst_list[-1]
|
||||||
a_pin = self.rightest_load_inst[last_driver_inst].get_pin("A")
|
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||||
offset=a_pin.center())
|
to_layer="m2",
|
||||||
mid_point = vector(a_pin.cx()+3*self.m2_width,a_pin.cy())
|
offset=a_pin.center())
|
||||||
self.add_path("metal2",[a_pin.center(), mid_point, mid_point.scale(1,0)])
|
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy())
|
||||||
|
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)])
|
||||||
self.add_layout_pin_segment_center(text="out",
|
self.add_layout_pin_segment_center(text="out",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
start=mid_point,
|
start=mid_point,
|
||||||
end=mid_point.scale(1,0))
|
end=mid_point.scale(1, 0))
|
||||||
|
|
||||||
def get_cin(self):
|
def get_cin(self):
|
||||||
"""Get the enable input ralative capacitance"""
|
"""Get the enable input ralative capacitance"""
|
||||||
#Only 1 input to the delay chain which is connected to an inverter.
|
# Only 1 input to the delay chain which is connected to an inverter.
|
||||||
dc_cin = self.inv.get_cin()
|
dc_cin = self.inv.get_cin()
|
||||||
return dc_cin
|
return dc_cin
|
||||||
|
|
||||||
def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True):
|
def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True):
|
||||||
"""Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load."""
|
"""Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load."""
|
||||||
stage_effort_list = []
|
stage_effort_list = []
|
||||||
#Add a stage to the list for every stage in delay chain. Stages only differ in fanout except the last which has an external cout.
|
# Add a stage to the list for every stage in delay chain.
|
||||||
|
# Stages only differ in fanout except the last which has an external cout.
|
||||||
last_stage_is_rise = inp_is_rise
|
last_stage_is_rise = inp_is_rise
|
||||||
for stage_fanout in self.fanout_list:
|
for stage_fanout in self.fanout_list:
|
||||||
stage_cout = self.inv.get_cin()*(stage_fanout+1)
|
stage_cout = self.inv.get_cin() * (stage_fanout + 1)
|
||||||
if len(stage_effort_list) == len(self.fanout_list)-1: #last stage
|
if len(stage_effort_list) == len(self.fanout_list) - 1:
|
||||||
stage_cout+=ext_delayed_en_cout
|
stage_cout+=ext_delayed_en_cout
|
||||||
stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise)
|
stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise)
|
||||||
stage_effort_list.append(stage)
|
stage_effort_list.append(stage)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,11 @@
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc
|
|
||||||
from math import log
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class dff_array(design.design):
|
class dff_array(design.design):
|
||||||
"""
|
"""
|
||||||
This is a simple row (or multiple rows) of flops.
|
This is a simple row (or multiple rows) of flops.
|
||||||
|
|
@ -52,41 +51,41 @@ class dff_array(design.design):
|
||||||
self.add_mod(self.dff)
|
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):
|
||||||
self.add_pin(self.get_din_name(row,col), "INPUT")
|
self.add_pin(self.get_din_name(row, col), "INPUT")
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
self.add_pin(self.get_dout_name(row,col), "OUTPUT")
|
self.add_pin(self.get_dout_name(row, col), "OUTPUT")
|
||||||
self.add_pin("clk", "INPUT")
|
self.add_pin("clk", "INPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def create_dff_array(self):
|
def create_dff_array(self):
|
||||||
self.dff_insts={}
|
self.dff_insts={}
|
||||||
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 = "dff_r{0}_c{1}".format(row,col)
|
name = "dff_r{0}_c{1}".format(row, col)
|
||||||
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)
|
||||||
self.connect_inst([self.get_din_name(row,col),
|
instance_ports = [self.get_din_name(row, col),
|
||||||
self.get_dout_name(row,col),
|
self.get_dout_name(row, col)]
|
||||||
"clk",
|
for port in self.dff.pin_names:
|
||||||
"vdd",
|
if port != 'D' and port != 'Q':
|
||||||
"gnd"])
|
instance_ports.append(port)
|
||||||
|
self.connect_inst(instance_ports)
|
||||||
|
|
||||||
def place_dff_array(self):
|
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 = "dff_r{0}_c{1}".format(row,col)
|
|
||||||
if (row % 2 == 0):
|
if (row % 2 == 0):
|
||||||
base = vector(col*self.dff.width,row*self.dff.height)
|
base = vector(col * self.dff.width, row * self.dff.height)
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
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].place(offset=base,
|
self.dff_insts[row, col].place(offset=base,
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
def get_din_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
|
|
@ -94,7 +93,7 @@ class dff_array(design.design):
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -104,61 +103,58 @@ class dff_array(design.design):
|
||||||
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 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.
|
# Continous vdd rail along with label.
|
||||||
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.center())
|
self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer)
|
||||||
|
|
||||||
# Continous gnd rail along with label.
|
# Continous gnd rail along with label.
|
||||||
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.center())
|
self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer)
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
for row in range(self.rows):
|
for col in range(self.columns):
|
||||||
for col in range(self.columns):
|
din_pin = self.dff_insts[row, col].get_pin("D")
|
||||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
debug.check(din_pin.layer == "m2", "DFF D pin not on metal2")
|
||||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
self.add_layout_pin(text=self.get_din_name(row, col),
|
||||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
|
||||||
layer=din_pin.layer,
|
layer=din_pin.layer,
|
||||||
offset=din_pin.ll(),
|
offset=din_pin.ll(),
|
||||||
width=din_pin.width(),
|
width=din_pin.width(),
|
||||||
height=din_pin.height())
|
height=din_pin.height())
|
||||||
|
|
||||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
dout_pin = self.dff_insts[row, col].get_pin("Q")
|
||||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
self.add_layout_pin(text=self.get_dout_name(row, col),
|
||||||
layer=dout_pin.layer,
|
layer=dout_pin.layer,
|
||||||
offset=dout_pin.ll(),
|
offset=dout_pin.ll(),
|
||||||
width=dout_pin.width(),
|
width=dout_pin.width(),
|
||||||
height=dout_pin.height())
|
height=dout_pin.height())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 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(self.dff.clk_pin)
|
||||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
clk_ypos = 2 * self.m3_pitch + self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2")
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
start=vector(0,clk_ypos),
|
start=vector(0, clk_ypos),
|
||||||
end=vector(self.width,clk_ypos))
|
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(self.dff.clk_pin)
|
||||||
# Make a vertical strip for each column
|
# Make a vertical strip for each column
|
||||||
self.add_rect(layer="metal2",
|
self.add_rect(layer="m2",
|
||||||
offset=clk_pin.ll().scale(1,0),
|
offset=clk_pin.ll().scale(1, 0),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
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_stack_center(from_layer=clk_pin.layer,
|
||||||
offset=vector(clk_pin.cx(),clk_ypos))
|
to_layer="m3",
|
||||||
|
offset=vector(clk_pin.cx(), clk_ypos))
|
||||||
|
|
||||||
def get_clk_cin(self):
|
def get_clk_cin(self):
|
||||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,13 @@
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc,parameter
|
from tech import parameter, layer
|
||||||
from math import log
|
from tech import cell_properties as props
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
||||||
|
|
||||||
class dff_buf(design.design):
|
class dff_buf(design.design):
|
||||||
"""
|
"""
|
||||||
This is a simple buffered DFF. The output is buffered
|
This is a simple buffered DFF. The output is buffered
|
||||||
|
|
@ -34,7 +35,7 @@ class dff_buf(design.design):
|
||||||
# This causes a DRC in the pinv which assumes min width rails. This ensures the output
|
# 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(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.inv1_size=inv1_size
|
||||||
self.inv2_size=inv2_size
|
self.inv2_size=inv2_size
|
||||||
|
|
@ -49,15 +50,14 @@ class dff_buf(design.design):
|
||||||
self.create_instances()
|
self.create_instances()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.width = self.dff.width + self.inv1.width + self.inv2.width
|
|
||||||
self.height = self.dff.height
|
|
||||||
|
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
|
self.width = self.inv2_inst.rx()
|
||||||
|
self.height = self.dff.height
|
||||||
self.route_wires()
|
self.route_wires()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.dff = factory.create(module_type="dff")
|
self.dff = factory.create(module_type="dff")
|
||||||
self.add_mod(self.dff)
|
self.add_mod(self.dff)
|
||||||
|
|
@ -71,8 +71,6 @@ class dff_buf(design.design):
|
||||||
size=self.inv2_size,
|
size=self.inv2_size,
|
||||||
height=self.dff.height)
|
height=self.dff.height)
|
||||||
self.add_mod(self.inv2)
|
self.add_mod(self.inv2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
self.add_pin("D", "INPUT")
|
self.add_pin("D", "INPUT")
|
||||||
|
|
@ -82,58 +80,76 @@ class dff_buf(design.design):
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
if props.dff_buff.add_body_contacts:
|
||||||
|
self.add_pin("vpb", "INPUT")
|
||||||
|
self.add_pin("vpn", "INPUT")
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
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)
|
||||||
|
|
||||||
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
|
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
|
||||||
|
|
||||||
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)
|
||||||
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
||||||
|
|
||||||
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)
|
||||||
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
||||||
|
|
||||||
def place_instances(self):
|
def place_instances(self):
|
||||||
# Add the DFF
|
# Add the DFF
|
||||||
self.dff_inst.place(vector(0,0))
|
self.dff_inst.place(vector(0, 0))
|
||||||
|
|
||||||
# Add INV1 to the right
|
# Add INV1 to the right
|
||||||
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
|
# The INV needs well spacing because the DFF is likely from a library
|
||||||
|
# with different well construction rules
|
||||||
|
well_spacing = 0
|
||||||
|
try:
|
||||||
|
well_spacing = max(well_spacing, self.nwell_space)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
well_spacing = max(well_spacing, self.pwell_space)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
well_spacing = max(well_spacing, self.pwell_to_nwell)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0))
|
||||||
|
|
||||||
# Add INV2 to the right
|
# Add INV2 to the right
|
||||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
self.inv2_inst.place(vector(self.inv1_inst.rx(), 0))
|
||||||
|
|
||||||
def route_wires(self):
|
def route_wires(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
|
|
||||||
# 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")
|
||||||
mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx())
|
mid1 = vector(a1_pin.cx(), q_pin.cy())
|
||||||
mid1 = vector(mid_x_offset, q_pin.cy())
|
self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height())
|
||||||
mid2 = vector(mid_x_offset, a1_pin.cy())
|
self.add_via_stack_center(from_layer=a1_pin.layer,
|
||||||
self.add_path("metal3", [q_pin.center(), mid1, mid2, a1_pin.center()])
|
to_layer=q_pin.layer,
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
offset=a1_pin.center())
|
||||||
offset=q_pin.center())
|
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
|
||||||
offset=a1_pin.center())
|
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
|
||||||
offset=a1_pin.center())
|
|
||||||
|
|
||||||
# Route inv1 z to inv2 a
|
# Route 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")
|
||||||
mid_x_offset = 0.5*(z1_pin.cx() + a2_pin.cx())
|
self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
|
||||||
self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy())
|
self.add_zjog(z1_pin.layer, z1_pin.center(), a2_pin.center())
|
||||||
mid2 = vector(mid_x_offset, a2_pin.cy())
|
|
||||||
self.add_path("metal1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()])
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
|
|
||||||
# Continous vdd rail along with label.
|
# Continous vdd rail along with label.
|
||||||
vdd_pin=self.dff_inst.get_pin("vdd")
|
vdd_pin=self.dff_inst.get_pin("vdd")
|
||||||
self.add_layout_pin(text="vdd",
|
self.add_layout_pin(text="vdd",
|
||||||
layer="metal1",
|
layer=vdd_pin.layer,
|
||||||
offset=vdd_pin.ll(),
|
offset=vdd_pin.ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=vdd_pin.height())
|
height=vdd_pin.height())
|
||||||
|
|
@ -141,7 +157,7 @@ class dff_buf(design.design):
|
||||||
# Continous gnd rail along with label.
|
# Continous gnd rail along with label.
|
||||||
gnd_pin=self.dff_inst.get_pin("gnd")
|
gnd_pin=self.dff_inst.get_pin("gnd")
|
||||||
self.add_layout_pin(text="gnd",
|
self.add_layout_pin(text="gnd",
|
||||||
layer="metal1",
|
layer=gnd_pin.layer,
|
||||||
offset=gnd_pin.ll(),
|
offset=gnd_pin.ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=vdd_pin.height())
|
height=vdd_pin.height())
|
||||||
|
|
@ -161,26 +177,29 @@ class dff_buf(design.design):
|
||||||
height=din_pin.height())
|
height=din_pin.height())
|
||||||
|
|
||||||
dout_pin = self.inv2_inst.get_pin("Z")
|
dout_pin = self.inv2_inst.get_pin("Z")
|
||||||
mid_pos = dout_pin.center() + vector(self.m1_pitch,0)
|
mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0)
|
||||||
q_pos = mid_pos - vector(0,self.m2_pitch)
|
q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch)
|
||||||
self.add_layout_pin_rect_center(text="Q",
|
self.add_layout_pin_rect_center(text="Q",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=q_pos)
|
offset=q_pos)
|
||||||
self.add_path("metal1", [dout_pin.center(), mid_pos, q_pos])
|
self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos])
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_stack_center(from_layer=dout_pin.layer,
|
||||||
offset=q_pos)
|
to_layer="m2",
|
||||||
|
offset=q_pos)
|
||||||
|
|
||||||
qb_pos = self.mid_qb_pos + vector(0,self.m2_pitch)
|
qb_pos = self.mid_qb_pos + vector(0, 2 * self.m2_nonpref_pitch)
|
||||||
self.add_layout_pin_rect_center(text="Qb",
|
self.add_layout_pin_rect_center(text="Qb",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=qb_pos)
|
offset=qb_pos)
|
||||||
self.add_path("metal1", [self.mid_qb_pos, qb_pos])
|
self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos])
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
a2_pin = self.inv2_inst.get_pin("A")
|
||||||
offset=qb_pos)
|
self.add_via_stack_center(from_layer=a2_pin.layer,
|
||||||
|
to_layer="m2",
|
||||||
|
offset=qb_pos)
|
||||||
|
|
||||||
def get_clk_cin(self):
|
def get_clk_cin(self):
|
||||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff"""
|
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff"""
|
||||||
#This is a handmade cell so the value must be entered in the tech.py file or estimated.
|
# This is a handmade cell so the value must be entered in the tech.py file or estimated.
|
||||||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
# Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||||
#FIXME: Dff changed in a past commit. The parameter need to be updated.
|
# FIXME: Dff changed in a past commit. The parameter need to be updated.
|
||||||
return parameter["dff_clk_cin"]
|
return parameter["dff_clk_cin"]
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc
|
from tech import cell_properties as props
|
||||||
from math import log
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
|
||||||
|
|
||||||
class dff_buf_array(design.design):
|
class dff_buf_array(design.design):
|
||||||
"""
|
"""
|
||||||
This is a simple row (or multiple rows) of flops.
|
This is a simple row (or multiple rows) of flops.
|
||||||
|
|
@ -48,22 +48,27 @@ class dff_buf_array(design.design):
|
||||||
self.width = self.columns * self.dff.width
|
self.width = self.columns * self.dff.width
|
||||||
self.height = self.rows * self.dff.height
|
self.height = self.rows * self.dff.height
|
||||||
self.place_dff_array()
|
self.place_dff_array()
|
||||||
|
self.route_supplies()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
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):
|
||||||
self.add_pin(self.get_din_name(row,col), "INPUT")
|
self.add_pin(self.get_din_name(row, col), "INPUT")
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
self.add_pin(self.get_dout_name(row,col), "OUTPUT")
|
self.add_pin(self.get_dout_name(row, col), "OUTPUT")
|
||||||
self.add_pin(self.get_dout_bar_name(row,col), "OUTPUT")
|
self.add_pin(self.get_dout_bar_name(row, col), "OUTPUT")
|
||||||
self.add_pin("clk", "INPUT")
|
self.add_pin("clk", "INPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
if props.dff_buff_array.add_body_contacts:
|
||||||
|
self.add_pin("vpb", "INPUT")
|
||||||
|
self.add_pin("vnb", "INPUT")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.dff = factory.create(module_type="dff_buf",
|
self.dff = factory.create(module_type="dff_buf",
|
||||||
inv1_size=self.inv1_size,
|
inv1_size=self.inv1_size,
|
||||||
|
|
@ -72,30 +77,51 @@ class dff_buf_array(design.design):
|
||||||
|
|
||||||
def create_dff_array(self):
|
def create_dff_array(self):
|
||||||
self.dff_insts={}
|
self.dff_insts={}
|
||||||
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 = "dff_r{0}_c{1}".format(row,col)
|
name = "dff_r{0}_c{1}".format(row, col)
|
||||||
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)
|
||||||
self.connect_inst([self.get_din_name(row,col),
|
inst_ports = [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),
|
||||||
"clk",
|
"clk",
|
||||||
"vdd",
|
"vdd",
|
||||||
"gnd"])
|
"gnd"]
|
||||||
|
if props.dff_buff_array.add_body_contacts:
|
||||||
|
inst_ports.append("vpb")
|
||||||
|
inst_ports.append("vnb")
|
||||||
|
self.connect_inst(inst_ports)
|
||||||
|
|
||||||
def place_dff_array(self):
|
def place_dff_array(self):
|
||||||
for row in range(self.rows):
|
|
||||||
|
well_spacing = 0
|
||||||
|
try:
|
||||||
|
well_spacing = max(self.nwell_space, well_spacing)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
well_spacing = max(self.pwell_space, well_spacing)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
well_spacing = max(self.pwell_to_nwell, well_spacing)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
dff_pitch = self.dff.width + well_spacing + self.well_extend_active
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
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):
|
if (row % 2 == 0):
|
||||||
base = vector(col*self.dff.width,row*self.dff.height)
|
base = vector(col * dff_pitch, row * self.dff.height)
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
else:
|
else:
|
||||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
base = vector(col * dff_pitch, (row + 1) * self.dff.height)
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
self.dff_insts[row,col].place(offset=base,
|
self.dff_insts[row, col].place(offset=base,
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
def get_din_name(self, row, col):
|
def get_din_name(self, row, col):
|
||||||
if self.columns == 1:
|
if self.columns == 1:
|
||||||
|
|
@ -103,7 +129,7 @@ class dff_buf_array(design.design):
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -113,7 +139,7 @@ class dff_buf_array(design.design):
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -123,75 +149,84 @@ class dff_buf_array(design.design):
|
||||||
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 route_supplies(self):
|
||||||
for row in range(self.rows):
|
for row in range(self.rows):
|
||||||
for col in range(self.columns):
|
vdd0_pin=self.dff_insts[row, 0].get_pin("vdd")
|
||||||
|
vddn_pin=self.dff_insts[row, self.columns - 1].get_pin("vdd")
|
||||||
|
self.add_path(vdd0_pin.layer, [vdd0_pin.lc(), vddn_pin.rc()], width=vdd0_pin.height())
|
||||||
|
|
||||||
|
gnd0_pin=self.dff_insts[row, 0].get_pin("gnd")
|
||||||
|
gndn_pin=self.dff_insts[row, self.columns - 1].get_pin("gnd")
|
||||||
|
self.add_path(gnd0_pin.layer, [gnd0_pin.lc(), gndn_pin.rc()], width=gnd0_pin.height())
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
for col in range(self.columns):
|
||||||
# Continous vdd rail along with label.
|
# Continous vdd rail along with label.
|
||||||
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(), start_layer=vdd_pin.layer)
|
||||||
|
|
||||||
# Continous gnd rail along with label.
|
# Continous gnd rail along with label.
|
||||||
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(), start_layer=gnd_pin.layer)
|
||||||
|
|
||||||
|
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):
|
din_pin = self.dff_insts[row, col].get_pin("D")
|
||||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
debug.check(din_pin.layer=="m2", "DFF D pin not on metal2")
|
||||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
self.add_layout_pin(text=self.get_din_name(row, col),
|
||||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
|
||||||
layer=din_pin.layer,
|
layer=din_pin.layer,
|
||||||
offset=din_pin.ll(),
|
offset=din_pin.ll(),
|
||||||
width=din_pin.width(),
|
width=din_pin.width(),
|
||||||
height=din_pin.height())
|
height=din_pin.height())
|
||||||
|
|
||||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
dout_pin = self.dff_insts[row, col].get_pin("Q")
|
||||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
debug.check(dout_pin.layer=="m2", "DFF Q pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
self.add_layout_pin(text=self.get_dout_name(row, col),
|
||||||
layer=dout_pin.layer,
|
layer=dout_pin.layer,
|
||||||
offset=dout_pin.ll(),
|
offset=dout_pin.ll(),
|
||||||
width=dout_pin.width(),
|
width=dout_pin.width(),
|
||||||
height=dout_pin.height())
|
height=dout_pin.height())
|
||||||
|
|
||||||
dout_bar_pin = self.dff_insts[row,col].get_pin("Qb")
|
dout_bar_pin = self.dff_insts[row, col].get_pin("Qb")
|
||||||
debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2")
|
debug.check(dout_bar_pin.layer=="m2", "DFF Qb pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_dout_bar_name(row,col),
|
self.add_layout_pin(text=self.get_dout_bar_name(row, col),
|
||||||
layer=dout_bar_pin.layer,
|
layer=dout_bar_pin.layer,
|
||||||
offset=dout_bar_pin.ll(),
|
offset=dout_bar_pin.ll(),
|
||||||
width=dout_bar_pin.width(),
|
width=dout_bar_pin.width(),
|
||||||
height=dout_bar_pin.height())
|
height=dout_bar_pin.height())
|
||||||
|
|
||||||
|
|
||||||
# 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
|
clk_ypos = 2 * self.m3_pitch + self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer=="m2", "DFF clk pin not on metal2")
|
||||||
if self.columns==1:
|
if self.columns==1:
|
||||||
self.add_layout_pin(text="clk",
|
self.add_layout_pin(text="clk",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=clk_pin.ll().scale(1,0),
|
offset=clk_pin.ll().scale(1, 0),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
height=self.height)
|
height=self.height)
|
||||||
else:
|
else:
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
start=vector(0,clk_ypos),
|
start=vector(0, clk_ypos),
|
||||||
end=vector(self.width,clk_ypos))
|
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
|
||||||
self.add_rect(layer="metal2",
|
self.add_rect(layer="m2",
|
||||||
offset=clk_pin.ll().scale(1,0),
|
offset=clk_pin.ll().scale(1, 0),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
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=self.m2_stack,
|
||||||
offset=vector(clk_pin.cx(),clk_ypos))
|
offset=vector(clk_pin.cx(), clk_ypos))
|
||||||
|
|
||||||
def get_clk_cin(self):
|
def get_clk_cin(self):
|
||||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||||
|
|
|
||||||
|
|
@ -97,13 +97,13 @@ class dff_inv(design.design):
|
||||||
mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx())
|
mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx())
|
||||||
mid1 = vector(mid_x_offset, q_pin.cy())
|
mid1 = vector(mid_x_offset, q_pin.cy())
|
||||||
mid2 = vector(mid_x_offset, a1_pin.cy())
|
mid2 = vector(mid_x_offset, a1_pin.cy())
|
||||||
self.add_path("metal3",
|
self.add_path("m3",
|
||||||
[q_pin.center(), mid1, mid2, a1_pin.center()])
|
[q_pin.center(), mid1, mid2, a1_pin.center()])
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=q_pin.center())
|
offset=q_pin.center())
|
||||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=a1_pin.center())
|
offset=a1_pin.center())
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=a1_pin.center())
|
offset=a1_pin.center())
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ class dff_inv(design.design):
|
||||||
# Continous vdd rail along with label.
|
# Continous vdd rail along with label.
|
||||||
vdd_pin=self.dff_inst.get_pin("vdd")
|
vdd_pin=self.dff_inst.get_pin("vdd")
|
||||||
self.add_layout_pin(text="vdd",
|
self.add_layout_pin(text="vdd",
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=vdd_pin.ll(),
|
offset=vdd_pin.ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=vdd_pin.height())
|
height=vdd_pin.height())
|
||||||
|
|
@ -120,7 +120,7 @@ class dff_inv(design.design):
|
||||||
# Continous gnd rail along with label.
|
# Continous gnd rail along with label.
|
||||||
gnd_pin=self.dff_inst.get_pin("gnd")
|
gnd_pin=self.dff_inst.get_pin("gnd")
|
||||||
self.add_layout_pin(text="gnd",
|
self.add_layout_pin(text="gnd",
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=gnd_pin.ll(),
|
offset=gnd_pin.ll(),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=vdd_pin.height())
|
height=vdd_pin.height())
|
||||||
|
|
@ -146,9 +146,9 @@ class dff_inv(design.design):
|
||||||
|
|
||||||
dout_pin = self.inv1_inst.get_pin("Z")
|
dout_pin = self.inv1_inst.get_pin("Z")
|
||||||
self.add_layout_pin_rect_center(text="Qb",
|
self.add_layout_pin_rect_center(text="Qb",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=dout_pin.center())
|
offset=dout_pin.center())
|
||||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=dout_pin.center())
|
offset=dout_pin.center())
|
||||||
|
|
||||||
def get_clk_cin(self):
|
def get_clk_cin(self):
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ 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):
|
||||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
din_pin = self.dff_insts[row,col].get_pin("D")
|
||||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
debug.check(din_pin.layer=="m2","DFF D pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
self.add_layout_pin(text=self.get_din_name(row,col),
|
||||||
layer=din_pin.layer,
|
layer=din_pin.layer,
|
||||||
offset=din_pin.ll(),
|
offset=din_pin.ll(),
|
||||||
|
|
@ -148,7 +148,7 @@ class dff_inv_array(design.design):
|
||||||
height=din_pin.height())
|
height=din_pin.height())
|
||||||
|
|
||||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
||||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
self.add_layout_pin(text=self.get_dout_name(row,col),
|
||||||
layer=dout_pin.layer,
|
layer=dout_pin.layer,
|
||||||
offset=dout_pin.ll(),
|
offset=dout_pin.ll(),
|
||||||
|
|
@ -156,7 +156,7 @@ class dff_inv_array(design.design):
|
||||||
height=dout_pin.height())
|
height=dout_pin.height())
|
||||||
|
|
||||||
dout_bar_pin = self.dff_insts[row,col].get_pin("Qb")
|
dout_bar_pin = self.dff_insts[row,col].get_pin("Qb")
|
||||||
debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2")
|
debug.check(dout_bar_pin.layer=="m2","DFF Qb pin not on metal2")
|
||||||
self.add_layout_pin(text=self.get_dout_bar_name(row,col),
|
self.add_layout_pin(text=self.get_dout_bar_name(row,col),
|
||||||
layer=dout_bar_pin.layer,
|
layer=dout_bar_pin.layer,
|
||||||
offset=dout_bar_pin.ll(),
|
offset=dout_bar_pin.ll(),
|
||||||
|
|
@ -167,27 +167,27 @@ 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
|
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2")
|
||||||
if self.columns==1:
|
if self.columns==1:
|
||||||
self.add_layout_pin(text="clk",
|
self.add_layout_pin(text="clk",
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=clk_pin.ll().scale(1,0),
|
offset=clk_pin.ll().scale(1,0),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
height=self.height)
|
height=self.height)
|
||||||
else:
|
else:
|
||||||
self.add_layout_pin_segment_center(text="clk",
|
self.add_layout_pin_segment_center(text="clk",
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
start=vector(0,clk_ypos),
|
start=vector(0,clk_ypos),
|
||||||
end=vector(self.width,clk_ypos))
|
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
|
||||||
self.add_rect(layer="metal2",
|
self.add_rect(layer="m2",
|
||||||
offset=clk_pin.ll().scale(1,0),
|
offset=clk_pin.ll().scale(1,0),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
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=self.m2_stack,
|
||||||
offset=vector(clk_pin.cx(),clk_ypos))
|
offset=vector(clk_pin.cx(),clk_ypos))
|
||||||
|
|
||||||
def get_clk_cin(self):
|
def get_clk_cin(self):
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,22 @@
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import debug
|
from bitcell_base_array import bitcell_base_array
|
||||||
import design
|
|
||||||
from tech import drc
|
|
||||||
import contact
|
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from vector import vector
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
class dummy_array(design.design):
|
|
||||||
|
class dummy_array(bitcell_base_array):
|
||||||
"""
|
"""
|
||||||
Generate a dummy row/column for the replica array.
|
Generate a dummy row/column for the replica array.
|
||||||
"""
|
"""
|
||||||
def __init__(self, cols, rows, mirror=0, name=""):
|
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
|
||||||
design.design.__init__(self, name)
|
super().__init__(cols, rows, name, column_offset)
|
||||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
|
||||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
|
||||||
|
|
||||||
self.column_size = cols
|
|
||||||
self.row_size = rows
|
|
||||||
self.mirror = mirror
|
self.mirror = mirror
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
""" Create and connect the netlist """
|
""" Create and connect the netlist """
|
||||||
|
|
@ -37,27 +28,7 @@ class dummy_array(design.design):
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
||||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||||
self.height = self.row_size*self.dummy_cell.height
|
|
||||||
self.width = self.column_size*self.dummy_cell.width
|
|
||||||
|
|
||||||
xoffset = 0.0
|
|
||||||
for col in range(self.column_size):
|
|
||||||
yoffset = 0.0
|
|
||||||
for row in range(self.row_size):
|
|
||||||
name = "dummy_r{0}_c{1}".format(row, col)
|
|
||||||
|
|
||||||
if (row+self.mirror) % 2:
|
|
||||||
tempy = yoffset + self.dummy_cell.height
|
|
||||||
dir_key = "MX"
|
|
||||||
else:
|
|
||||||
tempy = yoffset
|
|
||||||
dir_key = ""
|
|
||||||
|
|
||||||
self.cell_inst[row,col].place(offset=[xoffset, tempy],
|
|
||||||
mirror=dir_key)
|
|
||||||
yoffset += self.dummy_cell.height
|
|
||||||
xoffset += self.dummy_cell.width
|
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
|
|
@ -65,85 +36,22 @@ class dummy_array(design.design):
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
row_list = self.cell.get_all_wl_names()
|
|
||||||
column_list = self.cell.get_all_bitline_names()
|
|
||||||
for col in range(self.column_size):
|
|
||||||
for cell_column in column_list:
|
|
||||||
self.add_pin(cell_column+"_{0}".format(col), "INOUT")
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
|
||||||
self.add_pin(cell_row+"_{0}".format(row), "INPUT")
|
|
||||||
self.add_pin("vdd", "POWER")
|
|
||||||
self.add_pin("gnd", "GROUND")
|
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add the modules used in this design """
|
""" Add the modules used in this design """
|
||||||
self.dummy_cell = factory.create(module_type="dummy_bitcell")
|
self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
|
||||||
self.add_mod(self.dummy_cell)
|
self.add_mod(self.dummy_cell)
|
||||||
|
|
||||||
self.cell = factory.create(module_type="bitcell")
|
self.cell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
def get_bitcell_pins(self, col, row):
|
|
||||||
""" Creates a list of connections in the bitcell,
|
|
||||||
indexed by column and row, for instance use in bitcell_array """
|
|
||||||
|
|
||||||
bitcell_pins = []
|
|
||||||
|
|
||||||
pin_names = self.cell.get_all_bitline_names()
|
|
||||||
for pin in pin_names:
|
|
||||||
bitcell_pins.append(pin+"_{0}".format(col))
|
|
||||||
pin_names = self.cell.get_all_wl_names()
|
|
||||||
for pin in pin_names:
|
|
||||||
bitcell_pins.append(pin+"_{0}".format(row))
|
|
||||||
bitcell_pins.append("vdd")
|
|
||||||
bitcell_pins.append("gnd")
|
|
||||||
|
|
||||||
return bitcell_pins
|
|
||||||
|
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
""" Create the module instances used in this design """
|
""" Create the module instances used in this design """
|
||||||
self.cell_inst = {}
|
self.cell_inst = {}
|
||||||
for col in range(self.column_size):
|
for col in range(self.column_size):
|
||||||
for row in range(self.row_size):
|
for row in range(self.row_size):
|
||||||
name = "bit_r{0}_c{1}".format(row, col)
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
self.cell_inst[row,col]=self.add_inst(name=name,
|
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||||
mod=self.dummy_cell)
|
mod=self.dummy_cell)
|
||||||
self.connect_inst(self.get_bitcell_pins(col, row))
|
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||||
|
|
||||||
def add_layout_pins(self):
|
|
||||||
""" Add the layout pins """
|
|
||||||
|
|
||||||
row_list = self.cell.get_all_wl_names()
|
|
||||||
column_list = self.cell.get_all_bitline_names()
|
|
||||||
|
|
||||||
for col in range(self.column_size):
|
|
||||||
for cell_column in column_list:
|
|
||||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
|
||||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
|
||||||
layer="metal2",
|
|
||||||
offset=bl_pin.ll(),
|
|
||||||
width=bl_pin.width(),
|
|
||||||
height=self.height)
|
|
||||||
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for cell_row in row_list:
|
|
||||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
|
||||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
|
||||||
layer="metal1",
|
|
||||||
offset=wl_pin.ll(),
|
|
||||||
width=self.width,
|
|
||||||
height=wl_pin.height())
|
|
||||||
|
|
||||||
# For every second row and column, add a via for gnd and vdd
|
|
||||||
for row in range(self.row_size):
|
|
||||||
for col in range(self.column_size):
|
|
||||||
inst = self.cell_inst[row,col]
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
|
||||||
for pin in inst.get_pins(pin_name):
|
|
||||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
|
||||||
|
|
||||||
|
|
||||||
def input_load(self):
|
def input_load(self):
|
||||||
wl_wire = self.gen_wl_wire()
|
wl_wire = self.gen_wl_wire()
|
||||||
|
|
@ -151,7 +59,7 @@ class dummy_array(design.design):
|
||||||
|
|
||||||
def get_wordline_cin(self):
|
def get_wordline_cin(self):
|
||||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||||
total_cin = bitcell_wl_cin * self.column_size
|
total_cin = bitcell_wl_cin * self.column_size
|
||||||
return total_cin
|
return total_cin
|
||||||
|
|
|
||||||
|
|
@ -5,40 +5,37 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from math import log
|
|
||||||
from math import sqrt
|
|
||||||
import math
|
import math
|
||||||
import contact
|
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_decoder(design.design):
|
class hierarchical_decoder(design.design):
|
||||||
"""
|
"""
|
||||||
Dynamically generated hierarchical decoder.
|
Dynamically generated hierarchical decoder.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, rows, height=None):
|
def __init__(self, name, num_outputs):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
self.NAND_FORMAT = "DEC_NAND_{0}"
|
self.AND_FORMAT = "DEC_AND_{0}"
|
||||||
self.INV_FORMAT = "DEC_INV_{0}"
|
|
||||||
|
|
||||||
self.pre2x4_inst = []
|
self.pre2x4_inst = []
|
||||||
self.pre3x8_inst = []
|
self.pre3x8_inst = []
|
||||||
|
|
||||||
self.cell_height = height
|
b = factory.create(module_type="bitcell")
|
||||||
self.rows = rows
|
self.cell_height = b.height
|
||||||
self.num_inputs = int(math.log(self.rows, 2))
|
|
||||||
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
self.num_outputs = num_outputs
|
||||||
|
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
|
||||||
|
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.setup_netlist_constants()
|
self.setup_netlist_constants()
|
||||||
|
|
@ -50,23 +47,32 @@ class hierarchical_decoder(design.design):
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.place_pre_decoder()
|
self.place_pre_decoder()
|
||||||
self.place_row_decoder()
|
self.place_row_decoder()
|
||||||
self.route_input_rails()
|
|
||||||
self.route_predecode_rails()
|
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
|
||||||
|
|
||||||
|
self.route_inputs()
|
||||||
|
self.route_outputs()
|
||||||
|
self.route_decoder_bus()
|
||||||
self.route_vdd_gnd()
|
self.route_vdd_gnd()
|
||||||
|
|
||||||
self.offset_all_coordinates()
|
self.offset_all_coordinates()
|
||||||
|
|
||||||
|
self.width = self.and_inst[0].rx() + self.m1_space
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.inv = factory.create(module_type="pinv",
|
self.and2 = factory.create(module_type="and2_dec",
|
||||||
height=self.cell_height)
|
height=self.cell_height)
|
||||||
self.add_mod(self.inv)
|
self.add_mod(self.and2)
|
||||||
self.nand2 = factory.create(module_type="pnand2",
|
|
||||||
height=self.cell_height)
|
self.and3 = factory.create(module_type="and3_dec",
|
||||||
self.add_mod(self.nand2)
|
height=self.cell_height)
|
||||||
self.nand3 = factory.create(module_type="pnand3",
|
self.add_mod(self.and3)
|
||||||
height=self.cell_height)
|
# TBD
|
||||||
self.add_mod(self.nand3)
|
# self.and4 = factory.create(module_type="and4_dec")
|
||||||
|
# self.add_mod(self.and4)
|
||||||
|
|
||||||
self.add_decoders()
|
self.add_decoders()
|
||||||
|
|
||||||
|
|
@ -80,32 +86,32 @@ class hierarchical_decoder(design.design):
|
||||||
height=self.cell_height)
|
height=self.cell_height)
|
||||||
self.add_mod(self.pre3_8)
|
self.add_mod(self.pre3_8)
|
||||||
|
|
||||||
def determine_predecodes(self,num_inputs):
|
def determine_predecodes(self, num_inputs):
|
||||||
""" Determines the number of 2:4 pre-decoder and 3:8 pre-decoder
|
""" Determines the number of 2:4 pre-decoder and 3:8 pre-decoder
|
||||||
needed based on the number of inputs """
|
needed based on the number of inputs """
|
||||||
if (num_inputs == 2):
|
if (num_inputs == 2):
|
||||||
return (1,0)
|
return (1, 0)
|
||||||
elif (num_inputs == 3):
|
elif (num_inputs == 3):
|
||||||
return(0,1)
|
return(0, 1)
|
||||||
elif (num_inputs == 4):
|
elif (num_inputs == 4):
|
||||||
return(2,0)
|
return(2, 0)
|
||||||
elif (num_inputs == 5):
|
elif (num_inputs == 5):
|
||||||
return(1,1)
|
return(1, 1)
|
||||||
elif (num_inputs == 6):
|
elif (num_inputs == 6):
|
||||||
return(3,0)
|
return(3, 0)
|
||||||
elif (num_inputs == 7):
|
elif (num_inputs == 7):
|
||||||
return(2,1)
|
return(2, 1)
|
||||||
elif (num_inputs == 8):
|
elif (num_inputs == 8):
|
||||||
return(1,2)
|
return(1, 2)
|
||||||
elif (num_inputs == 9):
|
elif (num_inputs == 9):
|
||||||
return(0,3)
|
return(0, 3)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid number of inputs for hierarchical decoder",-1)
|
debug.error("Invalid number of inputs for hierarchical decoder", -1)
|
||||||
|
|
||||||
def setup_netlist_constants(self):
|
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 bus to different groups. One group belongs to one pre-decoder.
|
||||||
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
|
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
|
||||||
# have total 16 output lines out of these 3 pre-decoders and they will
|
# have total 16 output lines out of these 3 pre-decoders and they will
|
||||||
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||||
|
|
@ -125,107 +131,121 @@ class hierarchical_decoder(design.design):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.predec_groups.append(lines)
|
self.predec_groups.append(lines)
|
||||||
|
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
def setup_layout_constants(self):
|
||||||
""" Calculate the overall dimensions of the hierarchical decoder """
|
""" Calculate the overall 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.".format(self.num_inputs),-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:
|
# FIXME: Update with 4x16
|
||||||
self.predecoder_width = self.pre3_8.width
|
if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0:
|
||||||
|
self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width)
|
||||||
|
elif self.no_of_pre3x8 > 0:
|
||||||
|
self.predecoder_width = self.pre3_8.width
|
||||||
else:
|
else:
|
||||||
self.predecoder_width = self.pre2_4.width
|
self.predecoder_width = self.pre2_4.width
|
||||||
|
|
||||||
self.predecoder_height = self.pre2_4.height*self.no_of_pre2x4 + self.pre3_8.height*self.no_of_pre3x8
|
|
||||||
|
|
||||||
# Calculates height and width of row-decoder
|
# How much space between each predecoder
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
self.predecoder_spacing = 2 * self.and2.height
|
||||||
nand_width = self.nand2.width
|
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \
|
||||||
|
+ (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing
|
||||||
|
|
||||||
|
# Inputs to cells are on input layer
|
||||||
|
# Outputs from cells are on output layer
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
self.bus_layer = "m1"
|
||||||
|
self.bus_directions = "nonpref"
|
||||||
|
self.bus_pitch = self.m1_pitch
|
||||||
|
self.bus_space = self.m2_space
|
||||||
|
self.input_layer = "m2"
|
||||||
|
self.output_layer = "li"
|
||||||
|
self.output_layer_pitch = self.li_pitch
|
||||||
else:
|
else:
|
||||||
nand_width = self.nand3.width
|
self.bus_layer = "m2"
|
||||||
self.internal_routing_width = self.m2_pitch*self.total_number_of_predecoder_outputs
|
self.bus_directions = "pref"
|
||||||
self.row_decoder_height = self.inv.height * self.rows
|
self.bus_pitch = self.m2_pitch
|
||||||
|
self.bus_space = self.m2_space
|
||||||
|
# These two layers being the same requires a special jog
|
||||||
|
# to ensure to conflicts with the output layers
|
||||||
|
self.input_layer = "m1"
|
||||||
|
self.output_layer = "m3"
|
||||||
|
self.output_layer_pitch = self.m3_pitch
|
||||||
|
|
||||||
self.input_routing_width = (self.num_inputs+1) * self.m2_pitch
|
# Two extra pitches between modules on left and right
|
||||||
# Calculates height and width of hierarchical decoder
|
self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch
|
||||||
self.height = self.row_decoder_height
|
self.row_decoder_height = self.and2.height * self.num_outputs
|
||||||
self.width = self.input_routing_width + self.predecoder_width \
|
|
||||||
+ self.internal_routing_width + nand_width + self.inv.width
|
|
||||||
|
|
||||||
def route_input_rails(self):
|
|
||||||
""" Create input rails for the predecoders """
|
|
||||||
# inputs should be as high as the decoders
|
|
||||||
input_height = self.no_of_pre2x4*self.pre2_4.height + self.no_of_pre3x8*self.pre3_8.height
|
|
||||||
|
|
||||||
|
# Extra bus space for supply contacts
|
||||||
|
self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space
|
||||||
|
|
||||||
|
def route_inputs(self):
|
||||||
|
""" Create input bus for the predecoders """
|
||||||
# Find the left-most predecoder
|
# Find the left-most predecoder
|
||||||
min_x = 0
|
min_x = 0
|
||||||
if self.no_of_pre2x4 > 0:
|
if self.no_of_pre2x4 > 0:
|
||||||
min_x = min(min_x, -self.pre2_4.width)
|
min_x = min(min_x, self.pre2x4_inst[0].lx())
|
||||||
if self.no_of_pre3x8 > 0:
|
if self.no_of_pre3x8 > 0:
|
||||||
min_x = min(min_x, -self.pre3_8.width)
|
min_x = min(min_x, self.pre3x8_inst[0].lx())
|
||||||
input_offset=vector(min_x - self.input_routing_width,0)
|
input_offset=vector(min_x - self.input_routing_width, 0)
|
||||||
|
|
||||||
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||||
pitch=self.m2_pitch,
|
offset=input_offset,
|
||||||
offset=input_offset,
|
names=input_bus_names,
|
||||||
names=input_bus_names,
|
length=self.predecoder_height)
|
||||||
length=input_height)
|
|
||||||
|
|
||||||
self.route_input_to_predecodes()
|
self.route_input_to_predecodes()
|
||||||
|
|
||||||
|
|
||||||
def route_input_to_predecodes(self):
|
def route_input_to_predecodes(self):
|
||||||
""" Route the vertical input rail to the predecoders """
|
""" Route the vertical input rail to the predecoders """
|
||||||
for pre_num in range(self.no_of_pre2x4):
|
for pre_num in range(self.no_of_pre2x4):
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
index = pre_num * 2 + i
|
index = pre_num * 2 + i
|
||||||
|
|
||||||
input_pos = self.input_rails["addr_{}".format(index)]
|
input_pos = self.input_bus["addr_{}".format(index)].center()
|
||||||
|
|
||||||
in_name = "in_{}".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
decoder_offset = decoder_pin.center()
|
||||||
# that it aligns with the vdd/gnd rails
|
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||||
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)
|
self.route_input_bus(decoder_offset, input_offset)
|
||||||
|
|
||||||
|
|
||||||
for pre_num in range(self.no_of_pre3x8):
|
for pre_num in range(self.no_of_pre3x8):
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
||||||
|
|
||||||
input_pos = self.input_rails["addr_{}".format(index)]
|
input_pos = self.input_bus["addr_{}".format(index)].center()
|
||||||
|
|
||||||
in_name = "in_{}".format(i)
|
in_name = "in_{}".format(i)
|
||||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||||
|
|
||||||
# To prevent conflicts, we will offset each input connect so
|
decoder_offset = decoder_pin.center()
|
||||||
# that it aligns with the vdd/gnd rails
|
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||||
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)
|
self.route_input_bus(decoder_offset, input_offset)
|
||||||
|
|
||||||
|
|
||||||
def route_input_rail(self, input_offset, output_offset):
|
def route_input_bus(self, input_offset, output_offset):
|
||||||
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
|
"""
|
||||||
|
Route a vertical M2 coordinate to another
|
||||||
|
vertical M2 coordinate to the predecode inputs
|
||||||
|
"""
|
||||||
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
offset=input_offset)
|
to_layer=self.input_layer,
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
offset=input_offset)
|
||||||
offset=output_offset)
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
self.add_path(("metal3"), [input_offset, output_offset])
|
to_layer=self.input_layer,
|
||||||
|
offset=output_offset,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
self.add_path(self.input_layer, [input_offset, output_offset])
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
""" Add the module pins """
|
""" Add the module pins """
|
||||||
|
|
@ -233,12 +253,11 @@ class hierarchical_decoder(design.design):
|
||||||
for i in range(self.num_inputs):
|
for i in range(self.num_inputs):
|
||||||
self.add_pin("addr_{0}".format(i), "INPUT")
|
self.add_pin("addr_{0}".format(i), "INPUT")
|
||||||
|
|
||||||
for j in range(self.rows):
|
for j in range(self.num_outputs):
|
||||||
self.add_pin("decode_{0}".format(j), "OUTPUT")
|
self.add_pin("decode_{0}".format(j), "OUTPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
|
||||||
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] """
|
||||||
|
|
||||||
|
|
@ -248,7 +267,7 @@ class hierarchical_decoder(design.design):
|
||||||
for i in range(self.no_of_pre3x8):
|
for i in range(self.no_of_pre3x8):
|
||||||
self.create_pre3x8(i)
|
self.create_pre3x8(i)
|
||||||
|
|
||||||
def create_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):
|
||||||
|
|
@ -268,8 +287,7 @@ class hierarchical_decoder(design.design):
|
||||||
mod=self.pre2_4))
|
mod=self.pre2_4))
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
def create_pre3x8(self, num):
|
||||||
def create_pre3x8(self,num):
|
|
||||||
""" Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
""" Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||||
# If we had 2x4 predecodes, those are used as the lower
|
# If we had 2x4 predecodes, those are used as the lower
|
||||||
# decode output bits
|
# decode output bits
|
||||||
|
|
@ -283,11 +301,10 @@ class hierarchical_decoder(design.design):
|
||||||
pins.append("out_{0}".format(output_index + out_index_offset))
|
pins.append("out_{0}".format(output_index + out_index_offset))
|
||||||
pins.extend(["vdd", "gnd"])
|
pins.extend(["vdd", "gnd"])
|
||||||
|
|
||||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
||||||
mod=self.pre3_8))
|
mod=self.pre3_8))
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
||||||
def place_pre_decoder(self):
|
def place_pre_decoder(self):
|
||||||
""" Creates pre-decoder and places labels input address [A] """
|
""" Creates pre-decoder and places labels input address [A] """
|
||||||
|
|
||||||
|
|
@ -297,212 +314,151 @@ class hierarchical_decoder(design.design):
|
||||||
for i in range(self.no_of_pre3x8):
|
for i in range(self.no_of_pre3x8):
|
||||||
self.place_pre3x8(i)
|
self.place_pre3x8(i)
|
||||||
|
|
||||||
def place_pre2x4(self,num):
|
def place_pre2x4(self, num):
|
||||||
""" Place 2x4 predecoder to the left of the origin """
|
""" Place 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)
|
base = vector(-self.pre2_4.width, 0)
|
||||||
else:
|
else:
|
||||||
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
|
||||||
|
|
||||||
self.pre2x4_inst[num].place(base)
|
self.pre2x4_inst[num].place(base)
|
||||||
|
|
||||||
|
|
||||||
def place_pre3x8(self,num):
|
def place_pre3x8(self, num):
|
||||||
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||||
if (self.num_inputs == 3):
|
if (self.num_inputs == 3):
|
||||||
offset = vector(-self.pre_3_8.width,0)
|
offset = vector(-self.pre_3_8.width, 0)
|
||||||
mirror ="R0"
|
|
||||||
else:
|
else:
|
||||||
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
|
height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing)
|
||||||
offset = vector(-self.pre3_8.width, height)
|
offset = vector(-self.pre3_8.width, height)
|
||||||
|
|
||||||
self.pre3x8_inst[num].place(offset)
|
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 AND2/AND3 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.create_decoder_nand_array()
|
self.create_decoder_and_array()
|
||||||
self.create_decoder_inv_array()
|
|
||||||
|
|
||||||
|
def create_decoder_and_array(self):
|
||||||
|
""" Add a column of AND gates for final decode """
|
||||||
|
|
||||||
def create_decoder_nand_array(self):
|
self.and_inst = []
|
||||||
""" Add a column of NAND gates for final decode """
|
|
||||||
|
|
||||||
self.nand_inst = []
|
|
||||||
|
|
||||||
# Row Decoder NAND GATE array for address inputs <5.
|
# Row Decoder AND GATE array for address inputs <5.
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
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])):
|
||||||
row = len(self.predec_groups[0])*j + i
|
output = len(self.predec_groups[0]) * j + i
|
||||||
name = self.NAND_FORMAT.format(row)
|
if (output < self.num_outputs):
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
name = self.AND_FORMAT.format(output)
|
||||||
mod=self.nand2))
|
self.and_inst.append(self.add_inst(name=name,
|
||||||
pins =["out_{0}".format(i),
|
mod=self.and2))
|
||||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
pins =["out_{0}".format(i),
|
||||||
"Z_{0}".format(row),
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
"vdd", "gnd"]
|
"decode_{0}".format(output),
|
||||||
self.connect_inst(pins)
|
"vdd", "gnd"]
|
||||||
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
# Row Decoder AND 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):
|
||||||
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])):
|
||||||
row = (len(self.predec_groups[0])*len(self.predec_groups[1])) * k \
|
output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
|
||||||
+ len(self.predec_groups[0])*j + i
|
+ len(self.predec_groups[0]) * j + i
|
||||||
|
|
||||||
name = self.NAND_FORMAT.format(row)
|
if (output < self.num_outputs):
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
name = self.AND_FORMAT.format(output)
|
||||||
mod=self.nand3))
|
self.and_inst.append(self.add_inst(name=name,
|
||||||
|
mod=self.and3))
|
||||||
|
|
||||||
pins = ["out_{0}".format(i),
|
pins = ["out_{0}".format(i),
|
||||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||||
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||||
"Z_{0}".format(row),
|
"decode_{0}".format(output),
|
||||||
"vdd", "gnd"]
|
"vdd", "gnd"]
|
||||||
self.connect_inst(pins)
|
self.connect_inst(pins)
|
||||||
|
|
||||||
|
|
||||||
def create_decoder_inv_array(self):
|
|
||||||
"""
|
|
||||||
Add a column of INV gates for the decoder.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.inv_inst = []
|
|
||||||
for row in range(self.rows):
|
|
||||||
name = self.INV_FORMAT.format(row)
|
|
||||||
self.inv_inst.append(self.add_inst(name=name,
|
|
||||||
mod=self.inv))
|
|
||||||
self.connect_inst(args=["Z_{0}".format(row),
|
|
||||||
"decode_{0}".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def place_decoder_inv_array(self):
|
|
||||||
"""
|
|
||||||
Place the column of INV gates for the decoder above the predecoders
|
|
||||||
and to the right of the NAND decoders.
|
|
||||||
"""
|
|
||||||
|
|
||||||
z_pin = self.inv.get_pin("Z")
|
|
||||||
|
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
|
||||||
x_off = self.internal_routing_width + self.nand2.width
|
|
||||||
else:
|
|
||||||
x_off = self.internal_routing_width + self.nand3.width
|
|
||||||
|
|
||||||
for row in range(self.rows):
|
|
||||||
if (row % 2 == 0):
|
|
||||||
inv_row_height = self.inv.height * row
|
|
||||||
mirror = "R0"
|
|
||||||
y_dir = 1
|
|
||||||
else:
|
|
||||||
inv_row_height = self.inv.height * (row + 1)
|
|
||||||
mirror = "MX"
|
|
||||||
y_dir = -1
|
|
||||||
y_off = inv_row_height
|
|
||||||
offset = vector(x_off,y_off)
|
|
||||||
self.inv_inst[row].place(offset=offset,
|
|
||||||
mirror=mirror)
|
|
||||||
|
|
||||||
def place_row_decoder(self):
|
def place_row_decoder(self):
|
||||||
"""
|
"""
|
||||||
Place the row-decoder by placing NAND2/NAND3 and Inverters
|
Place the row-decoder by placing AND2/AND3 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.place_decoder_nand_array()
|
self.place_decoder_and_array()
|
||||||
self.place_decoder_inv_array()
|
|
||||||
self.route_decoder()
|
|
||||||
|
|
||||||
|
def place_decoder_and_array(self):
|
||||||
def place_decoder_nand_array(self):
|
"""
|
||||||
""" Add a column of NAND gates for final decode """
|
Add a column of AND gates for final decode.
|
||||||
|
This may have more than one decoder per row to match the bitcell height.
|
||||||
|
"""
|
||||||
|
|
||||||
# Row Decoder NAND GATE array for address inputs <5.
|
# Row Decoder AND GATE array for address inputs <5.
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
self.place_nand_array(nand_mod=self.nand2)
|
self.place_and_array(and_mod=self.and2)
|
||||||
|
|
||||||
# Row Decoder NAND GATE array for address inputs >5.
|
# Row Decoder AND GATE array for address inputs >5.
|
||||||
# FIXME: why this correct offset?)
|
# FIXME: why this correct offset?)
|
||||||
elif (self.num_inputs > 5):
|
elif (self.num_inputs > 5):
|
||||||
self.place_nand_array(nand_mod=self.nand3)
|
self.place_and_array(and_mod=self.and3)
|
||||||
|
|
||||||
def place_nand_array(self, nand_mod):
|
def place_and_array(self, and_mod):
|
||||||
""" Add a column of NAND gates for the decoder above the predecoders."""
|
"""
|
||||||
|
Add a column of AND gates for the decoder above the predecoders.
|
||||||
for row in range(self.rows):
|
"""
|
||||||
name = self.NAND_FORMAT.format(row)
|
|
||||||
|
for row in range(self.num_outputs):
|
||||||
if ((row % 2) == 0):
|
if ((row % 2) == 0):
|
||||||
y_off = nand_mod.height*row
|
y_off = and_mod.height * row
|
||||||
y_dir = 1
|
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
else:
|
else:
|
||||||
y_off = nand_mod.height*(row + 1)
|
y_off = and_mod.height * (row + 1)
|
||||||
y_dir = -1
|
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
|
|
||||||
self.nand_inst[row].place(offset=[self.internal_routing_width, y_off],
|
x_off = self.internal_routing_width
|
||||||
mirror=mirror)
|
self.and_inst[row].place(offset=vector(x_off, y_off),
|
||||||
|
mirror=mirror)
|
||||||
|
|
||||||
|
def route_outputs(self):
|
||||||
|
""" Add the pins. """
|
||||||
|
|
||||||
|
for row in range(self.num_outputs):
|
||||||
def route_decoder(self):
|
and_inst = self.and_inst[row]
|
||||||
""" Route the nand to inverter in the decoder and add the pins. """
|
self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row))
|
||||||
|
|
||||||
for row in range(self.rows):
|
|
||||||
|
|
||||||
# route nand output to output inv input
|
|
||||||
zr_pos = self.nand_inst[row].get_pin("Z").rc()
|
|
||||||
al_pos = self.inv_inst[row].get_pin("A").lc()
|
|
||||||
# ensure the bend is in the middle
|
|
||||||
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
|
|
||||||
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
|
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
|
||||||
|
|
||||||
z_pin = self.inv_inst[row].get_pin("Z")
|
|
||||||
self.add_layout_pin(text="decode_{0}".format(row),
|
|
||||||
layer="metal1",
|
|
||||||
offset=z_pin.ll(),
|
|
||||||
width=z_pin.width(),
|
|
||||||
height=z_pin.height())
|
|
||||||
|
|
||||||
|
def route_decoder_bus(self):
|
||||||
|
"""
|
||||||
def route_predecode_rails(self):
|
Creates vertical metal 2 bus 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)
|
# This leaves an offset for the predecoder output jogs
|
||||||
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
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_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||||
pitch=self.m2_pitch,
|
pitch=self.bus_pitch,
|
||||||
offset=input_offset,
|
offset=vector(self.bus_pitch, 0),
|
||||||
names=input_bus_names,
|
names=input_bus_names,
|
||||||
length=self.height)
|
length=self.height)
|
||||||
|
|
||||||
|
|
||||||
self.route_rails_to_predecodes()
|
self.route_predecodes_to_bus()
|
||||||
self.route_rails_to_decoder()
|
self.route_bus_to_decoder()
|
||||||
|
|
||||||
def route_rails_to_predecodes(self):
|
|
||||||
""" Iterates through all of the predecodes and connects to the rails including the offsets """
|
|
||||||
|
|
||||||
|
def route_predecodes_to_bus(self):
|
||||||
|
"""
|
||||||
|
Iterates through all of the predecodes
|
||||||
|
and connects to the rails including the offsets
|
||||||
|
"""
|
||||||
# FIXME: convert to connect_bus
|
# FIXME: convert to connect_bus
|
||||||
for pre_num in range(self.no_of_pre2x4):
|
for pre_num in range(self.no_of_pre2x4):
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||||
out_name = "out_{}".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||||
self.route_predecode_rail_m3(predecode_name, pin)
|
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
|
||||||
|
y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height
|
||||||
|
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||||
|
|
||||||
# FIXME: convert to connect_bus
|
# FIXME: convert to connect_bus
|
||||||
for pre_num in range(self.no_of_pre3x8):
|
for pre_num in range(self.no_of_pre3x8):
|
||||||
|
|
@ -510,91 +466,147 @@ class hierarchical_decoder(design.design):
|
||||||
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||||
out_name = "out_{}".format(i)
|
out_name = "out_{}".format(i)
|
||||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||||
self.route_predecode_rail_m3(predecode_name, pin)
|
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
|
||||||
|
y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
|
||||||
|
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||||
|
|
||||||
|
def route_bus_to_decoder(self):
|
||||||
|
|
||||||
def route_rails_to_decoder(self):
|
|
||||||
""" Use the self.predec_groups to determine the connections to the decoder NAND gates.
|
|
||||||
Inputs of NAND2/NAND3 gates come from different groups.
|
|
||||||
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
|
|
||||||
[8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to
|
|
||||||
[0,4,8] and second NAND3 is connected to [0,4,9] ........... and the
|
|
||||||
128th NAND3 is connected to [3,7,15]
|
|
||||||
"""
|
"""
|
||||||
row_index = 0
|
Use the self.predec_groups to determine the connections to the decoder AND gates.
|
||||||
|
Inputs of AND2/AND3 gates come from different groups.
|
||||||
|
For example for these groups
|
||||||
|
[ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||||
|
the first AND3 inputs are connected to [0,4,8],
|
||||||
|
second AND3 is connected to [0,4,9],
|
||||||
|
...
|
||||||
|
and the 128th AND3 is connected to [3,7,15]
|
||||||
|
"""
|
||||||
|
output_index = 0
|
||||||
|
|
||||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||||
for index_B in self.predec_groups[1]:
|
for index_B in self.predec_groups[1]:
|
||||||
for index_A in self.predec_groups[0]:
|
for index_A in self.predec_groups[0]:
|
||||||
# FIXME: convert to connect_bus?
|
# FIXME: convert to connect_bus?
|
||||||
predecode_name = "predecode_{}".format(index_A)
|
if (output_index < self.num_outputs):
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
predecode_name = "predecode_{}".format(index_B)
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
self.and_inst[output_index].get_pin("A"),
|
||||||
row_index = row_index + 1
|
output_index)
|
||||||
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
|
self.and_inst[output_index].get_pin("B"),
|
||||||
|
output_index)
|
||||||
|
output_index = output_index + 1
|
||||||
|
|
||||||
elif (self.num_inputs > 5):
|
elif (self.num_inputs > 5):
|
||||||
for index_C in self.predec_groups[2]:
|
for index_C in self.predec_groups[2]:
|
||||||
for index_B in self.predec_groups[1]:
|
for index_B in self.predec_groups[1]:
|
||||||
for index_A in self.predec_groups[0]:
|
for index_A in self.predec_groups[0]:
|
||||||
# FIXME: convert to connect_bus?
|
# FIXME: convert to connect_bus?
|
||||||
predecode_name = "predecode_{}".format(index_A)
|
if (output_index < self.num_outputs):
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
predecode_name = "predecode_{}".format(index_A)
|
||||||
predecode_name = "predecode_{}".format(index_B)
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
self.and_inst[output_index].get_pin("A"),
|
||||||
predecode_name = "predecode_{}".format(index_C)
|
output_index)
|
||||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
|
predecode_name = "predecode_{}".format(index_B)
|
||||||
row_index = row_index + 1
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
|
self.and_inst[output_index].get_pin("B"),
|
||||||
|
output_index)
|
||||||
|
predecode_name = "predecode_{}".format(index_C)
|
||||||
|
self.route_predecode_bus_outputs(predecode_name,
|
||||||
|
self.and_inst[output_index].get_pin("C"),
|
||||||
|
output_index)
|
||||||
|
output_index = output_index + 1
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
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
|
||||||
# The vias will be placed in the center and right of the cells, respectively.
|
must-connects next level up.
|
||||||
xoffset = self.nand_inst[0].cx()
|
"""
|
||||||
for num in range(0,self.rows):
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
|
||||||
# The nand and inv are the same height rows...
|
|
||||||
supply_pin = self.nand_inst[num].get_pin(pin_name)
|
|
||||||
pin_pos = vector(xoffset, supply_pin.cy())
|
|
||||||
self.add_power_pin(name=pin_name,
|
|
||||||
loc=pin_pos)
|
|
||||||
|
|
||||||
# Make a redundant rail too
|
|
||||||
for num in range(0,self.rows,2):
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
|
||||||
start = self.nand_inst[num].get_pin(pin_name).lc()
|
|
||||||
end = self.inv_inst[num].get_pin(pin_name).rc()
|
|
||||||
mid = (start+end).scale(0.5,0.5)
|
|
||||||
self.add_rect_center(layer="metal1",
|
|
||||||
offset=mid,
|
|
||||||
width=end.x-start.x)
|
|
||||||
|
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
# Copy the pins from the predecoders
|
for n in ["vdd", "gnd"]:
|
||||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
pins = self.and_inst[0].get_pins(n)
|
||||||
self.copy_layout_pin(pre, "vdd")
|
for pin in pins:
|
||||||
self.copy_layout_pin(pre, "gnd")
|
self.add_rect(layer=pin.layer,
|
||||||
|
offset=pin.ll() + vector(0, self.bus_space),
|
||||||
|
width=pin.width(),
|
||||||
|
height=self.height - 2 * self.bus_space)
|
||||||
|
|
||||||
|
# This adds power vias at the top of each cell
|
||||||
|
# (except the last to keep them inside the boundary)
|
||||||
|
for i in self.and_inst[:-1]:
|
||||||
|
pins = i.get_pins(n)
|
||||||
|
for pin in pins:
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
for i in self.pre2x4_inst + self.pre3x8_inst:
|
||||||
|
self.copy_layout_pin(i, n)
|
||||||
|
else:
|
||||||
|
# The vias will be placed at the right of the cells.
|
||||||
|
xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space
|
||||||
|
for row in range(0, self.num_outputs):
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
# The nand and inv are the same height rows...
|
||||||
|
supply_pin = self.and_inst[row].get_pin(pin_name)
|
||||||
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
|
self.add_power_pin(name=pin_name,
|
||||||
|
loc=pin_pos,
|
||||||
|
start_layer=supply_pin.layer)
|
||||||
|
|
||||||
|
# Copy the pins from the predecoders
|
||||||
|
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
self.copy_layout_pin(pre, pin_name)
|
||||||
|
|
||||||
|
def route_predecode_bus_outputs(self, rail_name, pin, row):
|
||||||
|
"""
|
||||||
|
Connect the routing rail to the given metal1 pin
|
||||||
|
using a routing track at the given y_offset
|
||||||
|
"""
|
||||||
|
|
||||||
def route_predecode_rail(self, rail_name, pin):
|
pin_pos = pin.center()
|
||||||
""" Connect the routing rail to the given metal1 pin """
|
rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
|
||||||
rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y)
|
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||||
self.add_path("metal1", [rail_pos, pin.lc()])
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
offset=rail_pos)
|
to_layer=self.input_layer,
|
||||||
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
def route_predecode_rail_m3(self, rail_name, pin):
|
|
||||||
""" Connect the routing rail to the given metal1 pin """
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.input_layer,
|
||||||
|
offset=pin_pos,
|
||||||
|
directions=("H", "H"))
|
||||||
|
|
||||||
|
def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset):
|
||||||
|
"""
|
||||||
|
Connect the routing rail to the given metal1 pin using a jog
|
||||||
|
to the right of the cell at the given x_offset.
|
||||||
|
"""
|
||||||
# This routes the pin up to the rail, basically, to avoid conflicts.
|
# 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.
|
||||||
mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2)
|
pin_pos = pin.rc()
|
||||||
rail_pos = vector(self.predecode_rails[rail_name].x,mid_point.y)
|
mid_point1 = vector(x_offset, pin_pos.y)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
mid_point2 = vector(x_offset, y_offset)
|
||||||
offset=pin.center())
|
rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y)
|
||||||
self.add_wire(("metal3","via2","metal2"), [rail_pos, mid_point, pin.uc()])
|
self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=rail_pos)
|
# pin_pos = pin.center()
|
||||||
|
# rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
|
||||||
|
# self.add_path(self.output_layer, [pin_pos, rail_pos])
|
||||||
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.output_layer,
|
||||||
|
offset=pin_pos)
|
||||||
|
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||||
|
to_layer=self.output_layer,
|
||||||
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
|
||||||
def input_load(self):
|
def input_load(self):
|
||||||
if self.determine_predecodes(self.num_inputs)[1]==0:
|
if self.determine_predecodes(self.num_inputs)[1]==0:
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,28 @@
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
import math
|
import math
|
||||||
from tech import drc
|
|
||||||
import contact
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode(design.design):
|
class hierarchical_predecode(design.design):
|
||||||
"""
|
"""
|
||||||
Pre 2x4 and 3x8 decoder shared code.
|
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, input_number, height=None):
|
def __init__(self, name, input_number, height=None):
|
||||||
self.number_of_inputs = input_number
|
self.number_of_inputs = input_number
|
||||||
self.cell_height = height
|
|
||||||
|
b = factory.create(module_type="bitcell")
|
||||||
|
if not height:
|
||||||
|
self.cell_height = b.height
|
||||||
|
self.column_decoder = False
|
||||||
|
else:
|
||||||
|
self.cell_height = height
|
||||||
|
# If we are pitch matched to the bitcell, it's a predecoder
|
||||||
|
# otherwise it's a column decoder (out of pgates)
|
||||||
|
self.column_decoder = (height != b.height)
|
||||||
|
|
||||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
|
|
@ -33,70 +42,103 @@ class hierarchical_predecode(design.design):
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Add the INV and NAND gate modules """
|
""" Add the INV and AND gate modules """
|
||||||
|
|
||||||
self.inv = factory.create(module_type="pinv",
|
|
||||||
height=self.cell_height)
|
|
||||||
self.add_mod(self.inv)
|
|
||||||
|
|
||||||
self.add_nand(self.number_of_inputs)
|
|
||||||
self.add_mod(self.nand)
|
|
||||||
|
|
||||||
def add_nand(self,inputs):
|
debug.check(self.number_of_inputs < 4,
|
||||||
""" Create the NAND for the predecode input stage """
|
"Invalid number of predecode inputs: {}".format(self.number_of_inputs))
|
||||||
if inputs==2:
|
|
||||||
self.nand = factory.create(module_type="pnand2",
|
|
||||||
height=self.cell_height)
|
|
||||||
elif inputs==3:
|
|
||||||
self.nand = factory.create(module_type="pnand3",
|
|
||||||
height=self.cell_height)
|
|
||||||
else:
|
|
||||||
debug.error("Invalid number of predecode inputs: {}".format(inputs),-1)
|
|
||||||
|
|
||||||
|
if self.column_decoder:
|
||||||
|
and_type = "pand{}".format(self.number_of_inputs)
|
||||||
|
inv_type = "pinv"
|
||||||
|
else:
|
||||||
|
and_type = "and{}_dec".format(self.number_of_inputs)
|
||||||
|
inv_type = "inv_dec"
|
||||||
|
self.and_mod = factory.create(module_type=and_type,
|
||||||
|
height=self.cell_height)
|
||||||
|
self.add_mod(self.and_mod)
|
||||||
|
|
||||||
|
# This uses the pinv_dec parameterized cell
|
||||||
|
self.inv = factory.create(module_type=inv_type,
|
||||||
|
height=self.cell_height,
|
||||||
|
size=1)
|
||||||
|
self.add_mod(self.inv)
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
""" The general organization is from left to right:
|
||||||
|
1) a set of M2 rails for input signals
|
||||||
|
2) a set of inverters to invert input signals
|
||||||
|
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||||
|
4) a set of AND gates for inversion
|
||||||
|
"""
|
||||||
|
self.setup_layout_constraints()
|
||||||
|
self.route_rails()
|
||||||
|
self.place_input_inverters()
|
||||||
|
self.place_and_array()
|
||||||
|
self.route()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
def setup_layout_constraints(self):
|
def setup_layout_constraints(self):
|
||||||
|
|
||||||
self.height = self.number_of_outputs * self.nand.height
|
# Inputs to cells are on input layer
|
||||||
|
# Outputs from cells are on output layer
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
self.bus_layer = "m1"
|
||||||
|
self.bus_directions = "nonpref"
|
||||||
|
self.bus_pitch = self.m1_pitch
|
||||||
|
self.bus_space = 1.5 * self.m1_space
|
||||||
|
self.input_layer = "m2"
|
||||||
|
self.output_layer = "li"
|
||||||
|
self.output_layer_pitch = self.li_pitch
|
||||||
|
else:
|
||||||
|
self.bus_layer = "m2"
|
||||||
|
self.bus_directions = "pref"
|
||||||
|
self.bus_pitch = self.m2_pitch
|
||||||
|
self.bus_space = self.m2_space
|
||||||
|
# This requires a special jog to ensure to conflicts with the output layers
|
||||||
|
self.input_layer = "m1"
|
||||||
|
self.output_layer = "m1"
|
||||||
|
self.output_layer_pitch = self.m1_pitch
|
||||||
|
|
||||||
|
self.height = self.number_of_outputs * self.and_mod.height
|
||||||
|
|
||||||
# x offset for input inverters
|
# x offset for input inverters
|
||||||
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
|
# +1 input for spacing for supply rail contacts
|
||||||
|
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
|
||||||
|
|
||||||
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
|
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
|
||||||
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch
|
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
|
||||||
|
|
||||||
# x offset to output inverters
|
# x offset to output inverters
|
||||||
self.x_off_inv_2 = self.x_off_nand + self.nand.width
|
self.width = self.x_off_and + self.and_mod.width
|
||||||
|
|
||||||
# Height width are computed
|
|
||||||
self.width = self.x_off_inv_2 + self.inv.width
|
|
||||||
|
|
||||||
def route_rails(self):
|
def route_rails(self):
|
||||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
||||||
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
offset = vector(0.5*self.m2_width,2*self.m1_width)
|
# Offsets for the perimeter spacing to other modules
|
||||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
# This uses m3 pitch to leave space for power routes
|
||||||
pitch=self.m2_pitch,
|
offset = vector(self.bus_pitch, self.bus_pitch)
|
||||||
offset=offset,
|
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||||
names=input_names,
|
offset=offset,
|
||||||
length=self.height - 2*self.m1_width)
|
names=input_names,
|
||||||
|
length=self.height - 2 * self.bus_pitch)
|
||||||
|
|
||||||
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||||
decode_names = invert_names + non_invert_names
|
decode_names = invert_names + non_invert_names
|
||||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width)
|
offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
|
||||||
self.decode_rails = self.create_vertical_bus(layer="metal2",
|
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||||
pitch=self.m2_pitch,
|
|
||||||
offset=offset,
|
offset=offset,
|
||||||
names=decode_names,
|
names=decode_names,
|
||||||
length=self.height - 2*self.m1_width)
|
length=self.height - 2 * self.bus_pitch)
|
||||||
|
|
||||||
|
|
||||||
def create_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.inv_inst = []
|
||||||
for inv_num in range(self.number_of_inputs):
|
for inv_num in range(self.number_of_inputs):
|
||||||
name = "pre_inv_{0}".format(inv_num)
|
name = "pre_inv_{0}".format(inv_num)
|
||||||
self.in_inst.append(self.add_inst(name=name,
|
self.inv_inst.append(self.add_inst(name=name,
|
||||||
mod=self.inv))
|
mod=self.inv))
|
||||||
self.connect_inst(["in_{0}".format(inv_num),
|
self.connect_inst(["in_{0}".format(inv_num),
|
||||||
"inbar_{0}".format(inv_num),
|
"inbar_{0}".format(inv_num),
|
||||||
"vdd", "gnd"])
|
"vdd", "gnd"])
|
||||||
|
|
@ -104,6 +146,7 @@ class hierarchical_predecode(design.design):
|
||||||
def place_input_inverters(self):
|
def place_input_inverters(self):
|
||||||
""" Place the input inverters to invert input signals for the decode stage. """
|
""" Place the input inverters to invert input signals for the decode stage. """
|
||||||
for inv_num in range(self.number_of_inputs):
|
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"
|
||||||
|
|
@ -111,178 +154,204 @@ 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[inv_num].place(offset=offset,
|
|
||||||
mirror=mirror)
|
|
||||||
|
|
||||||
def create_output_inverters(self):
|
|
||||||
""" Create inverters for the inverted output decode signals. """
|
|
||||||
self.inv_inst = []
|
|
||||||
for inv_num in range(self.number_of_outputs):
|
|
||||||
name = "pre_nand_inv_{}".format(inv_num)
|
|
||||||
self.inv_inst.append(self.add_inst(name=name,
|
|
||||||
mod=self.inv))
|
|
||||||
self.connect_inst(["Z_{}".format(inv_num),
|
|
||||||
"out_{}".format(inv_num),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def place_output_inverters(self):
|
|
||||||
""" Place inverters for the inverted output decode signals. """
|
|
||||||
for inv_num in range(self.number_of_outputs):
|
|
||||||
if (inv_num % 2 == 0):
|
|
||||||
y_off = inv_num * self.inv.height
|
|
||||||
mirror = "R0"
|
|
||||||
else:
|
|
||||||
y_off =(inv_num + 1)*self.inv.height
|
|
||||||
mirror = "MX"
|
|
||||||
offset = vector(self.x_off_inv_2, y_off)
|
|
||||||
self.inv_inst[inv_num].place(offset=offset,
|
self.inv_inst[inv_num].place(offset=offset,
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
|
def create_and_array(self, connections):
|
||||||
|
""" Create the AND stage for the decodes """
|
||||||
|
self.and_inst = []
|
||||||
|
for and_input in range(self.number_of_outputs):
|
||||||
|
inout = str(self.number_of_inputs) + "x" + str(self.number_of_outputs)
|
||||||
|
name = "Xpre{0}_and_{1}".format(inout, and_input)
|
||||||
|
self.and_inst.append(self.add_inst(name=name,
|
||||||
|
mod=self.and_mod))
|
||||||
|
self.connect_inst(connections[and_input])
|
||||||
|
|
||||||
def create_nand_array(self,connections):
|
def place_and_array(self):
|
||||||
""" Create the NAND stage for the decodes """
|
""" Place the AND stage for the decodes """
|
||||||
self.nand_inst = []
|
for and_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)
|
if (and_input % 2 == 0):
|
||||||
name = "Xpre{0}_nand_{1}".format(inout,nand_input)
|
y_off = and_input * self.and_mod.height
|
||||||
self.nand_inst.append(self.add_inst(name=name,
|
|
||||||
mod=self.nand))
|
|
||||||
self.connect_inst(connections[nand_input])
|
|
||||||
|
|
||||||
|
|
||||||
def place_nand_array(self):
|
|
||||||
""" Place the NAND stage for the decodes """
|
|
||||||
for nand_input in range(self.number_of_outputs):
|
|
||||||
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
|
||||||
if (nand_input % 2 == 0):
|
|
||||||
y_off = nand_input * self.inv.height
|
|
||||||
mirror = "R0"
|
mirror = "R0"
|
||||||
else:
|
else:
|
||||||
y_off = (nand_input + 1) * self.inv.height
|
y_off = (and_input + 1) * self.and_mod.height
|
||||||
mirror = "MX"
|
mirror = "MX"
|
||||||
offset = vector(self.x_off_nand, y_off)
|
offset = vector(self.x_off_and, y_off)
|
||||||
self.nand_inst[nand_input].place(offset=offset,
|
self.and_inst[and_input].place(offset=offset,
|
||||||
mirror=mirror)
|
mirror=mirror)
|
||||||
|
|
||||||
|
|
||||||
def route(self):
|
def route(self):
|
||||||
self.route_input_inverters()
|
self.route_input_inverters()
|
||||||
self.route_inputs_to_rails()
|
self.route_inputs_to_rails()
|
||||||
self.route_nand_to_rails()
|
self.route_and_to_rails()
|
||||||
self.route_output_inverters()
|
self.route_output_and()
|
||||||
self.route_vdd_gnd()
|
self.route_vdd_gnd()
|
||||||
|
|
||||||
def route_inputs_to_rails(self):
|
def route_inputs_to_rails(self):
|
||||||
""" Route the uninverted inputs to the second set of rails """
|
""" Route the uninverted inputs to the second set of rails """
|
||||||
for num in range(self.number_of_inputs):
|
|
||||||
# route one signal next to each vdd/gnd rail since this is
|
|
||||||
# typically where the p/n devices are and there are no
|
|
||||||
# pins in the nand gates.
|
|
||||||
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
|
|
||||||
in_pin = "in_{}".format(num)
|
|
||||||
a_pin = "A_{}".format(num)
|
|
||||||
in_pos = vector(self.input_rails[in_pin].x,y_offset)
|
|
||||||
a_pos = vector(self.decode_rails[a_pin].x,y_offset)
|
|
||||||
self.add_path("metal1",[in_pos, a_pos])
|
|
||||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
|
||||||
offset=[self.input_rails[in_pin].x, y_offset])
|
|
||||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
|
||||||
offset=[self.decode_rails[a_pin].x, y_offset])
|
|
||||||
|
|
||||||
def route_output_inverters(self):
|
top_and_gate = self.and_inst[-1]
|
||||||
|
for num in range(self.number_of_inputs):
|
||||||
|
if num == 0:
|
||||||
|
pin = top_and_gate.get_pin("A")
|
||||||
|
elif num == 1:
|
||||||
|
pin = top_and_gate.get_pin("B")
|
||||||
|
elif num == 2:
|
||||||
|
pin = top_and_gate.get_pin("C")
|
||||||
|
elif num == 3:
|
||||||
|
pin = top_and_gate.get_pin("D")
|
||||||
|
else:
|
||||||
|
debug.error("Too many inputs for predecoder.", -1)
|
||||||
|
y_offset = pin.cy()
|
||||||
|
in_pin = "in_{}".format(num)
|
||||||
|
a_pin = "A_{}".format(num)
|
||||||
|
in_pos = vector(self.input_rails[in_pin].cx(), y_offset)
|
||||||
|
a_pos = vector(self.decode_rails[a_pin].cx(), y_offset)
|
||||||
|
self.add_path(self.input_layer, [in_pos, a_pos])
|
||||||
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=[self.input_rails[in_pin].cx(), y_offset])
|
||||||
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=[self.decode_rails[a_pin].cx(), y_offset])
|
||||||
|
|
||||||
|
def route_output_and(self):
|
||||||
"""
|
"""
|
||||||
Route all conections of the outputs inverters
|
Route all conections of the outputs and gates
|
||||||
"""
|
"""
|
||||||
for num in range(self.number_of_outputs):
|
for num in range(self.number_of_outputs):
|
||||||
|
|
||||||
# route nand output to output inv input
|
z_pin = self.and_inst[num].get_pin("Z")
|
||||||
zr_pos = self.nand_inst[num].get_pin("Z").rc()
|
|
||||||
al_pos = self.inv_inst[num].get_pin("A").lc()
|
|
||||||
# ensure the bend is in the middle
|
|
||||||
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
|
|
||||||
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
|
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
|
||||||
|
|
||||||
z_pin = self.inv_inst[num].get_pin("Z")
|
|
||||||
self.add_layout_pin(text="out_{}".format(num),
|
self.add_layout_pin(text="out_{}".format(num),
|
||||||
layer="metal1",
|
layer=z_pin.layer,
|
||||||
offset=z_pin.ll(),
|
offset=z_pin.ll(),
|
||||||
height=z_pin.height(),
|
height=z_pin.height(),
|
||||||
width=z_pin.width())
|
width=z_pin.width())
|
||||||
|
|
||||||
|
|
||||||
def route_input_inverters(self):
|
def route_input_inverters(self):
|
||||||
"""
|
"""
|
||||||
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
||||||
"""
|
"""
|
||||||
for inv_num in range(self.number_of_inputs):
|
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)
|
||||||
|
|
||||||
|
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
|
||||||
|
|
||||||
#add output so that it is just below the vdd or gnd rail
|
# add output so that it is just below the vdd or gnd rail
|
||||||
# since this is where the p/n devices are and there are no
|
# since this is where the p/n devices are and there are no
|
||||||
# pins in the nand gates.
|
# pins in the and gates.
|
||||||
y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space
|
inv_out_pos = inv_out_pin.rc()
|
||||||
inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc()
|
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
|
||||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0)
|
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
|
||||||
rail_pos = vector(self.decode_rails[out_pin].x,y_offset)
|
rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
|
||||||
self.add_path("metal1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
||||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
|
||||||
offset=rail_pos)
|
self.add_via_stack_center(from_layer=inv_out_pin.layer,
|
||||||
|
to_layer=self.output_layer,
|
||||||
|
offset=inv_out_pos)
|
||||||
|
self.add_via_stack_center(from_layer=self.output_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
|
||||||
#route input
|
# route input
|
||||||
inv_in_pos = self.in_inst[inv_num].get_pin("A").lc()
|
pin = self.inv_inst[inv_num].get_pin("A")
|
||||||
in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y)
|
inv_in_pos = pin.center()
|
||||||
self.add_path("metal1", [in_pos, inv_in_pos])
|
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_path(self.input_layer, [in_pos, inv_in_pos])
|
||||||
offset=in_pos)
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.input_layer,
|
||||||
|
offset=inv_in_pos)
|
||||||
|
via=self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=in_pos)
|
||||||
|
# Create the input pin at this location on the rail
|
||||||
|
self.add_layout_pin_rect_center(text=in_pin,
|
||||||
|
layer=self.bus_layer,
|
||||||
|
offset=in_pos,
|
||||||
|
height=via.mod.second_layer_height,
|
||||||
|
width=via.mod.second_layer_width)
|
||||||
|
|
||||||
|
def route_and_to_rails(self):
|
||||||
def route_nand_to_rails(self):
|
# This 2D array defines the connection mapping
|
||||||
# This 2D array defines the connection mapping
|
and_input_line_combination = self.get_and_input_line_combination()
|
||||||
nand_input_line_combination = self.get_nand_input_line_combination()
|
|
||||||
for k in range(self.number_of_outputs):
|
for k in range(self.number_of_outputs):
|
||||||
# create x offset list
|
# create x offset list
|
||||||
index_lst= nand_input_line_combination[k]
|
index_lst= and_input_line_combination[k]
|
||||||
|
|
||||||
if self.number_of_inputs == 2:
|
if self.number_of_inputs == 2:
|
||||||
gate_lst = ["A","B"]
|
gate_lst = ["A", "B"]
|
||||||
else:
|
else:
|
||||||
gate_lst = ["A","B","C"]
|
gate_lst = ["A", "B", "C"]
|
||||||
|
|
||||||
# this will connect pins A,B or A,B,C
|
# this will connect pins A,B or A,B,C
|
||||||
for rail_pin,gate_pin in zip(index_lst,gate_lst):
|
for rail_pin, gate_pin in zip(index_lst, gate_lst):
|
||||||
pin_pos = self.nand_inst[k].get_pin(gate_pin).lc()
|
pin = self.and_inst[k].get_pin(gate_pin)
|
||||||
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
|
pin_pos = pin.center()
|
||||||
self.add_path("metal1", [rail_pos, pin_pos])
|
rail_pos = vector(self.decode_rails[rail_pin].cx(), pin_pos.y)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||||
offset=rail_pos)
|
self.add_via_stack_center(from_layer=self.input_layer,
|
||||||
|
to_layer=self.bus_layer,
|
||||||
|
offset=rail_pos,
|
||||||
|
directions=self.bus_directions)
|
||||||
|
if gate_pin == "A":
|
||||||
|
direction = None
|
||||||
|
else:
|
||||||
|
direction = ("H", "H")
|
||||||
|
|
||||||
|
self.add_via_stack_center(from_layer=pin.layer,
|
||||||
|
to_layer=self.input_layer,
|
||||||
|
offset=pin_pos,
|
||||||
|
directions=direction)
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
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
|
# In sky130, we use hand-made decoder cells with vertical power
|
||||||
in_xoffset = self.in_inst[0].rx()
|
if OPTS.tech_name == "sky130" and not self.column_decoder:
|
||||||
out_xoffset = self.inv_inst[0].lx() - self.m1_space
|
|
||||||
for num in range(0,self.number_of_outputs):
|
|
||||||
# this will result in duplicate polygons for rails, but who cares
|
|
||||||
|
|
||||||
# Route both supplies
|
|
||||||
for n in ["vdd", "gnd"]:
|
for n in ["vdd", "gnd"]:
|
||||||
nand_pin = self.nand_inst[num].get_pin(n)
|
# This makes a wire from top to bottom for both inv and and gates
|
||||||
supply_offset = nand_pin.ll().scale(0,1)
|
for i in [self.inv_inst, self.and_inst]:
|
||||||
self.add_rect(layer="metal1",
|
bot_pins = i[0].get_pins(n)
|
||||||
offset=supply_offset,
|
top_pins = i[-1].get_pins(n)
|
||||||
width=self.inv_inst[num].rx())
|
for (bot_pin, top_pin) in zip(bot_pins, top_pins):
|
||||||
|
self.add_rect(layer=bot_pin.layer,
|
||||||
|
offset=vector(bot_pin.lx(), self.bus_pitch),
|
||||||
|
width=bot_pin.width(),
|
||||||
|
height=top_pin.uy() - self.bus_pitch)
|
||||||
|
# This adds power vias at the top of each cell
|
||||||
|
# (except the last to keep them inside the boundary)
|
||||||
|
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
|
||||||
|
pins = i.get_pins(n)
|
||||||
|
for pin in pins:
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin.uc(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
# In other techs, we are using standard cell decoder cells with horizontal power
|
||||||
|
else:
|
||||||
|
for num in range(0, self.number_of_outputs):
|
||||||
|
|
||||||
# Add pins in two locations
|
# Route both supplies
|
||||||
for xoffset in [in_xoffset, out_xoffset]:
|
for n in ["vdd", "gnd"]:
|
||||||
pin_pos = vector(xoffset, nand_pin.cy())
|
and_pins = self.and_inst[num].get_pins(n)
|
||||||
self.add_power_pin(n, pin_pos)
|
for and_pin in and_pins:
|
||||||
|
self.add_segment_center(layer=and_pin.layer,
|
||||||
|
start=vector(0, and_pin.cy()),
|
||||||
|
end=vector(self.width, and_pin.cy()))
|
||||||
|
|
||||||
|
# Add pins in two locations
|
||||||
|
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
|
||||||
|
self.and_inst[0].lx() - self.bus_space]:
|
||||||
|
pin_pos = vector(xoffset, and_pin.cy())
|
||||||
|
self.add_power_pin(name=n,
|
||||||
|
loc=pin_pos,
|
||||||
|
start_layer=and_pin.layer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,10 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
from vector import vector
|
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode2x4(hierarchical_predecode):
|
class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
Pre 2x4 decoder used in hierarchical_decoder.
|
Pre 2x4 decoder used in hierarchical_decoder.
|
||||||
|
|
@ -27,33 +24,16 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.create_input_inverters()
|
self.create_input_inverters()
|
||||||
self.create_output_inverters()
|
connections =[["inbar_0", "inbar_1", "out_0", "vdd", "gnd"],
|
||||||
connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"],
|
["in_0", "inbar_1", "out_1", "vdd", "gnd"],
|
||||||
["in_0", "inbar_1", "Z_1", "vdd", "gnd"],
|
["inbar_0", "in_1", "out_2", "vdd", "gnd"],
|
||||||
["inbar_0", "in_1", "Z_2", "vdd", "gnd"],
|
["in_0", "in_1", "out_3", "vdd", "gnd"]]
|
||||||
["in_0", "in_1", "Z_3", "vdd", "gnd"]]
|
self.create_and_array(connections)
|
||||||
self.create_nand_array(connections)
|
|
||||||
|
|
||||||
def create_layout(self):
|
def get_and_input_line_combination(self):
|
||||||
""" The general organization is from left to right:
|
""" These are the decoder connections of the AND gates to the A,B pins """
|
||||||
1) a set of M2 rails for input signals
|
|
||||||
2) a set of inverters to invert input signals
|
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
|
||||||
4) a set of NAND gates for inversion
|
|
||||||
"""
|
|
||||||
self.setup_layout_constraints()
|
|
||||||
self.route_rails()
|
|
||||||
self.place_input_inverters()
|
|
||||||
self.place_output_inverters()
|
|
||||||
self.place_nand_array()
|
|
||||||
self.route()
|
|
||||||
self.add_boundary()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def get_nand_input_line_combination(self):
|
|
||||||
""" These are the decoder connections of the NAND gates to the A,B pins """
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,10 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from tech import drc
|
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
from vector import vector
|
|
||||||
from hierarchical_predecode import hierarchical_predecode
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class hierarchical_predecode3x8(hierarchical_predecode):
|
class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
"""
|
"""
|
||||||
Pre 3x8 decoder used in hierarchical_decoder.
|
Pre 3x8 decoder used in hierarchical_decoder.
|
||||||
|
|
@ -20,49 +17,31 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
||||||
hierarchical_predecode.__init__(self, name, 3, height)
|
hierarchical_predecode.__init__(self, name, 3, height)
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.create_input_inverters()
|
self.create_input_inverters()
|
||||||
self.create_output_inverters()
|
connections=[["inbar_0", "inbar_1", "inbar_2", "out_0", "vdd", "gnd"],
|
||||||
connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"],
|
["in_0", "inbar_1", "inbar_2", "out_1", "vdd", "gnd"],
|
||||||
["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"],
|
["inbar_0", "in_1", "inbar_2", "out_2", "vdd", "gnd"],
|
||||||
["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"],
|
["in_0", "in_1", "inbar_2", "out_3", "vdd", "gnd"],
|
||||||
["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"],
|
["inbar_0", "inbar_1", "in_2", "out_4", "vdd", "gnd"],
|
||||||
["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"],
|
["in_0", "inbar_1", "in_2", "out_5", "vdd", "gnd"],
|
||||||
["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"],
|
["inbar_0", "in_1", "in_2", "out_6", "vdd", "gnd"],
|
||||||
["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"],
|
["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]]
|
||||||
["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]]
|
self.create_and_array(connections)
|
||||||
self.create_nand_array(connections)
|
|
||||||
|
|
||||||
def create_layout(self):
|
def get_and_input_line_combination(self):
|
||||||
"""
|
|
||||||
The general organization is from left to right:
|
|
||||||
1) a set of M2 rails for input signals
|
|
||||||
2) a set of inverters to invert input signals
|
|
||||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
|
||||||
4) a set of NAND gates for inversion
|
|
||||||
"""
|
|
||||||
self.setup_layout_constraints()
|
|
||||||
self.route_rails()
|
|
||||||
self.place_input_inverters()
|
|
||||||
self.place_output_inverters()
|
|
||||||
self.place_nand_array()
|
|
||||||
self.route()
|
|
||||||
self.add_boundary()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def get_nand_input_line_combination(self):
|
|
||||||
""" These are the decoder connections of the NAND gates to the A,B,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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
from hierarchical_predecode import hierarchical_predecode
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
|
class hierarchical_predecode4x16(hierarchical_predecode):
|
||||||
|
"""
|
||||||
|
Pre 4x16 decoder used in hierarchical_decoder.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, height=None):
|
||||||
|
hierarchical_predecode.__init__(self, name, 4, height)
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_pins()
|
||||||
|
self.add_modules()
|
||||||
|
self.create_input_inverters()
|
||||||
|
connections=[["inbar_0", "inbar_1", "inbar_2", "inbar_3", "out_0", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "inbar_2", "inbar_3", "out_1", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "inbar_2", "inbar_3", "out_2", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "inbar_2", "inbar_3", "out_3", "vdd", "gnd"],
|
||||||
|
["inbar_0", "inbar_1", "in_2", "inbar_3", "out_4", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"],
|
||||||
|
["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"],
|
||||||
|
["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"],
|
||||||
|
["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"],
|
||||||
|
["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"],
|
||||||
|
["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ]
|
||||||
|
|
||||||
|
self.create_and_array(connections)
|
||||||
|
|
||||||
|
def get_and_input_line_combination(self):
|
||||||
|
""" These are the decoder connections of the AND gates to the A,B pins """
|
||||||
|
combination = [["Abar_0", "Abar_1", "Abar_2", "Abar_3"],
|
||||||
|
["A_0", "Abar_1", "Abar_2", "Abar_3"],
|
||||||
|
["Abar_0", "A_1", "Abar_2", "Abar_3"],
|
||||||
|
["A_0", "A_1", "Abar_2", "Abar_3"],
|
||||||
|
["Abar_0", "Abar_1", "A_2" , "Abar_3"],
|
||||||
|
["A_0", "Abar_1", "A_2" , "Abar_3"],
|
||||||
|
["Abar_0", "A_1", "A_2" , "Abar_3"],
|
||||||
|
["A_0", "A_1", "A_2" , "Abar_3"],
|
||||||
|
["Abar_0", "Abar_1", "Abar_2", "A_3"],
|
||||||
|
["A_0", "Abar_1", "Abar_2", "A_3"],
|
||||||
|
["Abar_0", "A_1", "Abar_2", "A_3"],
|
||||||
|
["A_0", "A_1", "Abar_2", "A_3"],
|
||||||
|
["Abar_0", "Abar_1", "A_2", "A_3"],
|
||||||
|
["A_0", "Abar_1", "A_2", "A_3"],
|
||||||
|
["Abar_0", "A_1", "A_2", "A_3"],
|
||||||
|
["A_0", "A_1", "A_2", "A_3"]]
|
||||||
|
return combination
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
|
||||||
|
class module_type():
|
||||||
|
"""
|
||||||
|
This is a class that maps cell names to python classes implementing them.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.names = {}
|
||||||
|
|
||||||
|
def __setitem__(self, b, c):
|
||||||
|
self.names[b] = c
|
||||||
|
|
||||||
|
def is_overridden(self, b):
|
||||||
|
return (b in self.names.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, b):
|
||||||
|
if b not in self.names.keys():
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
return self.names[b]
|
||||||
|
|
@ -160,7 +160,7 @@ class multibank(design.design):
|
||||||
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
|
||||||
|
|
||||||
# A space for wells or jogging m2
|
# A space for wells or jogging m2
|
||||||
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"),
|
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclose_active"),
|
||||||
2*self.m2_pitch)
|
2*self.m2_pitch)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -451,14 +451,14 @@ class multibank(design.design):
|
||||||
# Connect the inverter output to the central bus
|
# Connect the inverter output to the central bus
|
||||||
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
||||||
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
||||||
self.add_path("metal3",[out_pos, bus_pos])
|
self.add_path("m3",[out_pos, bus_pos])
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=bus_pos,
|
offset=bus_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=out_pos,
|
offset=out_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=out_pos,
|
offset=out_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
@ -512,7 +512,7 @@ class multibank(design.design):
|
||||||
# 2 pitches on the right for vias/jogs to access the inputs
|
# 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_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0)
|
||||||
control_bus_length = self.bitcell_array_inst.uy()
|
control_bus_length = self.bitcell_array_inst.uy()
|
||||||
self.bus_xoffset = self.create_vertical_bus(layer="metal2",
|
self.bus_xoffset = self.create_vertical_bus(layer="m2",
|
||||||
pitch=self.m2_pitch,
|
pitch=self.m2_pitch,
|
||||||
offset=control_bus_offset,
|
offset=control_bus_offset,
|
||||||
names=self.control_signals,
|
names=self.control_signals,
|
||||||
|
|
@ -530,9 +530,9 @@ class multibank(design.design):
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
||||||
|
|
||||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
self.add_path("m2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||||
self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset),
|
self.add_path("m2",[precharge_br, vector(precharge_br.x,yoffset),
|
||||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -550,9 +550,9 @@ class multibank(design.design):
|
||||||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||||
|
|
||||||
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
self.add_path("m2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||||
self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
self.add_path("m2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
||||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||||
|
|
||||||
def route_sense_amp_to_col_mux_or_bitcell_array(self):
|
def route_sense_amp_to_col_mux_or_bitcell_array(self):
|
||||||
|
|
@ -573,9 +573,9 @@ class multibank(design.design):
|
||||||
|
|
||||||
|
|
||||||
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||||
self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
self.add_path("m2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
||||||
vector(connect_bl.x,yoffset), connect_bl])
|
vector(connect_bl.x,yoffset), connect_bl])
|
||||||
self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
self.add_path("m2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
||||||
vector(connect_br.x,yoffset), connect_br])
|
vector(connect_br.x,yoffset), connect_br])
|
||||||
|
|
||||||
def route_sense_amp_to_trigate(self):
|
def route_sense_amp_to_trigate(self):
|
||||||
|
|
@ -586,11 +586,11 @@ class multibank(design.design):
|
||||||
tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc()
|
tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc()
|
||||||
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
||||||
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=tri_gate_in)
|
offset=tri_gate_in)
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=sa_data_out)
|
offset=sa_data_out)
|
||||||
self.add_path("metal3",[sa_data_out,tri_gate_in])
|
self.add_path("m3",[sa_data_out,tri_gate_in])
|
||||||
|
|
||||||
def route_sense_amp_out(self):
|
def route_sense_amp_out(self):
|
||||||
""" Add pins for the sense amp output """
|
""" Add pins for the sense amp output """
|
||||||
|
|
@ -644,14 +644,14 @@ class multibank(design.design):
|
||||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc()
|
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc()
|
||||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||||
|
|
||||||
# The mid guarantees we exit the input cell to the right.
|
# The mid guarantees we exit the input cell to the right.
|
||||||
driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc()
|
driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc()
|
||||||
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc()
|
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc()
|
||||||
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
||||||
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||||
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -698,8 +698,8 @@ class multibank(design.design):
|
||||||
else:
|
else:
|
||||||
mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y)
|
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)
|
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_wire(self.m1_stack,[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||||
self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
self.add_path("m1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -715,7 +715,7 @@ class multibank(design.design):
|
||||||
wl_name = "wl_{}".format(i)
|
wl_name = "wl_{}".format(i)
|
||||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||||
self.add_label(text=wl_name,
|
self.add_label(text=wl_name,
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=wl_pin.center())
|
offset=wl_pin.center())
|
||||||
|
|
||||||
# Add the bitline names
|
# Add the bitline names
|
||||||
|
|
@ -725,10 +725,10 @@ class multibank(design.design):
|
||||||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||||
self.add_label(text=bl_name,
|
self.add_label(text=bl_name,
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=bl_pin.center())
|
offset=bl_pin.center())
|
||||||
self.add_label(text=br_name,
|
self.add_label(text=br_name,
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=br_pin.center())
|
offset=br_pin.center())
|
||||||
|
|
||||||
# # Add the data output names to the sense amp output
|
# # Add the data output names to the sense amp output
|
||||||
|
|
@ -736,7 +736,7 @@ class multibank(design.design):
|
||||||
# data_name = "data_{}".format(i)
|
# data_name = "data_{}".format(i)
|
||||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||||
# self.add_label(text="sa_out_{}".format(i),
|
# self.add_label(text="sa_out_{}".format(i),
|
||||||
# layer="metal2",
|
# layer="m2",
|
||||||
# offset=data_pin.center())
|
# offset=data_pin.center())
|
||||||
|
|
||||||
# Add labels on the decoder
|
# Add labels on the decoder
|
||||||
|
|
@ -745,7 +745,7 @@ class multibank(design.design):
|
||||||
pin_name = "in_{}".format(i)
|
pin_name = "in_{}".format(i)
|
||||||
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||||
self.add_label(text=data_name,
|
self.add_label(text=data_name,
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
offset=data_pin.center())
|
offset=data_pin.center())
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -765,8 +765,8 @@ class multibank(design.design):
|
||||||
|
|
||||||
for (control_signal, pin_pos) in connection:
|
for (control_signal, pin_pos) in connection:
|
||||||
control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y)
|
control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y)
|
||||||
self.add_path("metal1", [control_pos, pin_pos])
|
self.add_path("m1", [control_pos, pin_pos])
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=control_pos,
|
offset=control_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
@ -776,9 +776,9 @@ class multibank(design.design):
|
||||||
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
||||||
control_x_offset = self.bus_xoffset[control_signal].x
|
control_x_offset = self.bus_xoffset[control_signal].x
|
||||||
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
||||||
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos])
|
||||||
control_via_pos = vector(control_x_offset, mid_pos.y)
|
control_via_pos = vector(control_x_offset, mid_pos.y)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_center(layers=self.m1_stack,
|
||||||
offset=control_via_pos,
|
offset=control_via_pos,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
@ -791,13 +791,13 @@ class multibank(design.design):
|
||||||
if self.num_banks > 1:
|
if self.num_banks > 1:
|
||||||
# it's not an input pin if we have multiple banks
|
# it's not an input pin if we have multiple banks
|
||||||
self.add_label_pin(text=ctrl,
|
self.add_label_pin(text=ctrl,
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=vector(x_offset, self.min_y_offset),
|
offset=vector(x_offset, self.min_y_offset),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
height=self.max_y_offset-self.min_y_offset)
|
height=self.max_y_offset-self.min_y_offset)
|
||||||
else:
|
else:
|
||||||
self.add_layout_pin(text=ctrl,
|
self.add_layout_pin(text=ctrl,
|
||||||
layer="metal2",
|
layer="m2",
|
||||||
offset=vector(x_offset, self.min_y_offset),
|
offset=vector(x_offset, self.min_y_offset),
|
||||||
width=self.m2_width,
|
width=self.m2_width,
|
||||||
height=self.max_y_offset-self.min_y_offset)
|
height=self.max_y_offset-self.min_y_offset)
|
||||||
|
|
@ -807,12 +807,12 @@ class multibank(design.design):
|
||||||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||||
in_pin = inst.get_pin(pin).lc()
|
in_pin = inst.get_pin(pin).lc()
|
||||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||||
# Bring it up to M2 for M2/M3 routing
|
# Bring it up to M2 for M2/M3 routing
|
||||||
self.add_via(layers=("metal1","via1","metal2"),
|
self.add_via(layers=self.m1_stack,
|
||||||
offset=in_pin + contact.m1m2.offset,
|
offset=in_pin + contact.m1_via.offset,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
self.add_via(layers=("metal2","via2","metal3"),
|
self.add_via(layers=self.m2_stack,
|
||||||
offset=in_pin + self.m2m3_via_offset,
|
offset=in_pin + self.m2m3_via_offset,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
||||||
|
|
@ -821,10 +821,10 @@ class multibank(design.design):
|
||||||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||||
in_pin = inst.get_pin(pin).rc()
|
in_pin = inst.get_pin(pin).rc()
|
||||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||||
self.add_via(layers=("metal1","via1","metal2"),
|
self.add_via(layers=self.m1_stack,
|
||||||
offset=in_pin + contact.m1m2.offset,
|
offset=in_pin + contact.m1_via.offset,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
self.add_via(layers=("metal2","via2","metal3"),
|
self.add_via(layers=self.m2_stack,
|
||||||
offset=in_pin + self.m2m3_via_offset,
|
offset=in_pin + self.m2m3_via_offset,
|
||||||
rotate=90)
|
rotate=90)
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,15 @@
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import sys
|
from math import log, ceil
|
||||||
from tech import drc, parameter
|
|
||||||
from math import log
|
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from vector import vector
|
from vector import vector
|
||||||
|
from tech import layer
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class port_address(design.design):
|
class port_address(design.design):
|
||||||
"""
|
"""
|
||||||
Create the address port (row decoder and wordline driver)..
|
Create the address port (row decoder and wordline driver)..
|
||||||
|
|
@ -22,20 +21,19 @@ class port_address(design.design):
|
||||||
|
|
||||||
self.num_cols = cols
|
self.num_cols = cols
|
||||||
self.num_rows = rows
|
self.num_rows = rows
|
||||||
self.addr_size = int(log(self.num_rows, 2))
|
self.addr_size = ceil(log(self.num_rows, 2))
|
||||||
|
|
||||||
if name == "":
|
if name == "":
|
||||||
name = "port_address_{0}_{1}".format(cols,rows)
|
name = "port_address_{0}_{1}".format(cols, rows)
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(2, "create data port of cols {0} rows {1}".format(cols,rows))
|
debug.info(2, "create data port of cols {0} rows {1}".format(cols, rows))
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
|
debug.check(len(self.all_ports) <= 2, "Bank layout cannot handle more than two ports.")
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
|
|
@ -43,6 +41,10 @@ class port_address(design.design):
|
||||||
self.create_wordline_driver()
|
self.create_wordline_driver()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
self.route_layout()
|
self.route_layout()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
@ -51,16 +53,15 @@ class port_address(design.design):
|
||||||
""" Adding pins for port address module"""
|
""" Adding pins for port address module"""
|
||||||
|
|
||||||
for bit in range(self.addr_size):
|
for bit in range(self.addr_size):
|
||||||
self.add_pin("addr_{0}".format(bit),"INPUT")
|
self.add_pin("addr_{0}".format(bit), "INPUT")
|
||||||
|
|
||||||
self.add_pin("wl_en", "INPUT")
|
self.add_pin("wl_en", "INPUT")
|
||||||
|
|
||||||
for bit in range(self.num_rows):
|
for bit in range(self.num_rows):
|
||||||
self.add_pin("wl_{0}".format(bit),"OUTPUT")
|
self.add_pin("wl_{0}".format(bit), "OUTPUT")
|
||||||
|
|
||||||
self.add_pin("vdd","POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd","GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
|
||||||
def route_layout(self):
|
def route_layout(self):
|
||||||
""" Create routing amoung the modules """
|
""" Create routing amoung the modules """
|
||||||
|
|
@ -71,8 +72,8 @@ class port_address(design.design):
|
||||||
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 """
|
||||||
for inst in self.insts:
|
for inst in self.insts:
|
||||||
self.copy_power_pins(inst,"vdd")
|
self.copy_power_pins(inst, "vdd")
|
||||||
self.copy_power_pins(inst,"gnd")
|
self.copy_power_pins(inst, "gnd")
|
||||||
|
|
||||||
def route_pins(self):
|
def route_pins(self):
|
||||||
for row in range(self.addr_size):
|
for row in range(self.addr_size):
|
||||||
|
|
@ -88,31 +89,35 @@ class port_address(design.design):
|
||||||
def route_internal(self):
|
def route_internal(self):
|
||||||
for row in range(self.num_rows):
|
for row in range(self.num_rows):
|
||||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc()
|
decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row))
|
||||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc()
|
decoder_out_pos = decoder_out_pin.rc()
|
||||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row))
|
||||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
driver_in_pos = driver_in_pin.lc()
|
||||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3)
|
||||||
|
|
||||||
|
self.add_via_stack_center(from_layer=decoder_out_pin.layer,
|
||||||
|
to_layer=self.route_layer,
|
||||||
|
offset=decoder_out_pos)
|
||||||
|
|
||||||
|
self.add_via_stack_center(from_layer=driver_in_pin.layer,
|
||||||
|
to_layer=self.route_layer,
|
||||||
|
offset=driver_in_pos)
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
|
|
||||||
self.row_decoder = factory.create(module_type="decoder",
|
self.row_decoder = factory.create(module_type="decoder",
|
||||||
rows=self.num_rows)
|
num_outputs=self.num_rows)
|
||||||
self.add_mod(self.row_decoder)
|
self.add_mod(self.row_decoder)
|
||||||
|
|
||||||
self.wordline_driver = factory.create(module_type="wordline_driver",
|
self.wordline_driver = factory.create(module_type="wordline_driver_array",
|
||||||
rows=self.num_rows,
|
rows=self.num_rows,
|
||||||
cols=self.num_cols)
|
cols=self.num_cols)
|
||||||
self.add_mod(self.wordline_driver)
|
self.add_mod(self.wordline_driver)
|
||||||
|
|
||||||
|
|
||||||
def create_row_decoder(self):
|
def create_row_decoder(self):
|
||||||
""" Create the hierarchical row decoder """
|
""" Create the hierarchical row decoder """
|
||||||
|
|
||||||
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
||||||
mod=self.row_decoder)
|
mod=self.row_decoder)
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
|
|
@ -123,12 +128,10 @@ class port_address(design.design):
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_wordline_driver(self):
|
def create_wordline_driver(self):
|
||||||
""" Create the Wordline Driver """
|
""" Create the Wordline Driver """
|
||||||
|
|
||||||
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
|
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
|
||||||
mod=self.wordline_driver)
|
mod=self.wordline_driver)
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
|
|
@ -141,20 +144,13 @@ class port_address(design.design):
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def place_instances(self):
|
def place_instances(self):
|
||||||
"""
|
"""
|
||||||
Compute the offsets and place the instances.
|
Compute the offsets and place the instances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# A space for wells or jogging m2
|
row_decoder_offset = vector(0, 0)
|
||||||
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
wordline_driver_offset = vector(self.row_decoder.width, 0)
|
||||||
3*self.m2_pitch)
|
|
||||||
|
|
||||||
row_decoder_offset = vector(0,0)
|
|
||||||
wordline_driver_offset = vector(self.row_decoder.width + self.m2_gap,0)
|
|
||||||
|
|
||||||
self.wordline_driver_inst.place(wordline_driver_offset)
|
self.wordline_driver_inst.place(wordline_driver_offset)
|
||||||
self.row_decoder_inst.place(row_decoder_offset)
|
self.row_decoder_inst.place(row_decoder_offset)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
# See LICENSE for licensing information.
|
# See LICENSE for licensing information.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import sys
|
from tech import drc
|
||||||
from tech import drc, parameter
|
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
|
from collections import namedtuple
|
||||||
from vector import vector
|
from vector import vector
|
||||||
|
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class port_data(design.design):
|
class port_data(design.design):
|
||||||
"""
|
"""
|
||||||
Create the data port (column mux, sense amps, write driver, etc.) for the given port number.
|
Create the data port (column mux, sense amps, write driver, etc.) for the given port number.
|
||||||
|
|
@ -19,25 +19,52 @@ class port_data(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sram_config, port, name=""):
|
def __init__(self, sram_config, port, name=""):
|
||||||
|
|
||||||
sram_config.set_local_config(self)
|
sram_config.set_local_config(self)
|
||||||
self.port = port
|
self.port = port
|
||||||
if self.write_size is not None:
|
if self.write_size is not None:
|
||||||
self.num_wmasks = int(self.word_size/self.write_size)
|
self.num_wmasks = int(self.word_size / self.write_size)
|
||||||
else:
|
else:
|
||||||
self.num_wmasks = 0
|
self.num_wmasks = 0
|
||||||
|
|
||||||
|
if self.num_spare_cols is None:
|
||||||
|
self.num_spare_cols = 0
|
||||||
|
|
||||||
if name == "":
|
if name == "":
|
||||||
name = "port_data_{0}".format(self.port)
|
name = "port_data_{0}".format(self.port)
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(2, "create data port of size {0} with {1} words per row".format(self.word_size,self.words_per_row))
|
debug.info(2,
|
||||||
|
"create data port of size {0} with {1} words per row".format(self.word_size,
|
||||||
|
self.words_per_row))
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
|
debug.check(len(self.all_ports)<=2,
|
||||||
|
"Bank layout cannot handle more than two ports.")
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
|
|
||||||
|
def get_bl_names(self):
|
||||||
|
# bl lines are connect from the precharger
|
||||||
|
return self.precharge.get_bl_names()
|
||||||
|
|
||||||
|
def get_br_names(self):
|
||||||
|
# br lines are connect from the precharger
|
||||||
|
return self.precharge.get_br_names()
|
||||||
|
|
||||||
|
def get_bl_name(self, port=0):
|
||||||
|
bl_name = "bl"
|
||||||
|
if len(self.all_ports) == 1:
|
||||||
|
return bl_name
|
||||||
|
else:
|
||||||
|
return bl_name + "{}".format(port)
|
||||||
|
|
||||||
|
def get_br_name(self, port=0):
|
||||||
|
br_name = "br"
|
||||||
|
if len(self.all_ports) == 1:
|
||||||
|
return br_name
|
||||||
|
else:
|
||||||
|
return br_name + "{}".format(port)
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.precompute_constants()
|
self.precompute_constants()
|
||||||
|
|
@ -71,8 +98,6 @@ class port_data(design.design):
|
||||||
else:
|
else:
|
||||||
self.column_mux_array_inst = None
|
self.column_mux_array_inst = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.compute_instance_offsets()
|
self.compute_instance_offsets()
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
|
|
@ -80,33 +105,42 @@ class port_data(design.design):
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
""" Adding pins for port address module"""
|
""" Adding pins for port data module"""
|
||||||
|
|
||||||
self.add_pin("rbl_bl","INOUT")
|
self.add_pin("rbl_bl", "INOUT")
|
||||||
self.add_pin("rbl_br","INOUT")
|
self.add_pin("rbl_br", "INOUT")
|
||||||
for bit in range(self.num_cols):
|
for bit in range(self.num_cols):
|
||||||
self.add_pin("{0}_{1}".format(self.bl_names[self.port], bit),"INOUT")
|
bl_name = self.get_bl_name(self.port)
|
||||||
self.add_pin("{0}_{1}".format(self.br_names[self.port], bit),"INOUT")
|
br_name = self.get_br_name(self.port)
|
||||||
|
self.add_pin("{0}_{1}".format(bl_name, bit), "INOUT")
|
||||||
|
self.add_pin("{0}_{1}".format(br_name, bit), "INOUT")
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
bl_name = self.get_bl_name(self.port)
|
||||||
|
br_name = self.get_br_name(self.port)
|
||||||
|
self.add_pin("spare{0}_{1}".format(bl_name, bit), "INOUT")
|
||||||
|
self.add_pin("spare{0}_{1}".format(br_name, bit), "INOUT")
|
||||||
|
|
||||||
if self.port in self.read_ports:
|
if self.port in self.read_ports:
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
self.add_pin("dout_{}".format(bit),"OUTPUT")
|
self.add_pin("dout_{}".format(bit), "OUTPUT")
|
||||||
if self.port in self.write_ports:
|
if self.port in self.write_ports:
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
self.add_pin("din_{}".format(bit),"INPUT")
|
self.add_pin("din_{}".format(bit), "INPUT")
|
||||||
# Will be empty if no col addr lines
|
# Will be empty if no col addr lines
|
||||||
sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)]
|
sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)]
|
||||||
for pin_name in sel_names:
|
for pin_name in sel_names:
|
||||||
self.add_pin(pin_name,"INPUT")
|
self.add_pin(pin_name, "INPUT")
|
||||||
if self.port in self.read_ports:
|
if self.port in self.read_ports:
|
||||||
self.add_pin("s_en", "INPUT")
|
self.add_pin("s_en", "INPUT")
|
||||||
self.add_pin("p_en_bar", "INPUT")
|
self.add_pin("p_en_bar", "INPUT")
|
||||||
if self.port in self.write_ports:
|
if self.port in self.write_ports:
|
||||||
self.add_pin("w_en", "INPUT")
|
self.add_pin("w_en", "INPUT")
|
||||||
for bit in range(self.num_wmasks):
|
for bit in range(self.num_wmasks):
|
||||||
self.add_pin("bank_wmask_{}".format(bit),"INPUT")
|
self.add_pin("bank_wmask_{}".format(bit), "INPUT")
|
||||||
self.add_pin("vdd","POWER")
|
for bit in range(self.num_spare_cols):
|
||||||
self.add_pin("gnd","GROUND")
|
self.add_pin("bank_spare_wen{}".format(bit), "INPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def route_layout(self):
|
def route_layout(self):
|
||||||
""" Create routing among the modules """
|
""" Create routing among the modules """
|
||||||
|
|
@ -148,29 +182,34 @@ class port_data(design.design):
|
||||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||||
|
|
||||||
for inst in self.insts:
|
for inst in self.insts:
|
||||||
self.copy_power_pins(inst,"vdd")
|
self.copy_power_pins(inst, "vdd")
|
||||||
self.copy_power_pins(inst,"gnd")
|
self.copy_power_pins(inst, "gnd")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
|
|
||||||
# Extra column +1 is for RBL
|
# Extra column +1 is for RBL
|
||||||
# Precharge will be shifted left if needed
|
# Precharge will be shifted left if needed
|
||||||
|
# Column offset is set to port so extra column can be on left or right
|
||||||
|
# and mirroring happens correctly
|
||||||
self.precharge_array = factory.create(module_type="precharge_array",
|
self.precharge_array = factory.create(module_type="precharge_array",
|
||||||
columns=self.num_cols + 1,
|
columns=self.num_cols + self.num_spare_cols + 1,
|
||||||
bitcell_bl=self.bl_names[self.port],
|
bitcell_bl=self.bl_names[self.port],
|
||||||
bitcell_br=self.br_names[self.port])
|
bitcell_br=self.br_names[self.port],
|
||||||
|
column_offset=self.port - 1)
|
||||||
self.add_mod(self.precharge_array)
|
self.add_mod(self.precharge_array)
|
||||||
|
|
||||||
if self.port in self.read_ports:
|
if self.port in self.read_ports:
|
||||||
|
# RBLs don't get a sense amp
|
||||||
self.sense_amp_array = factory.create(module_type="sense_amp_array",
|
self.sense_amp_array = factory.create(module_type="sense_amp_array",
|
||||||
word_size=self.word_size,
|
word_size=self.word_size,
|
||||||
words_per_row=self.words_per_row)
|
words_per_row=self.words_per_row,
|
||||||
|
num_spare_cols=self.num_spare_cols)
|
||||||
self.add_mod(self.sense_amp_array)
|
self.add_mod(self.sense_amp_array)
|
||||||
else:
|
else:
|
||||||
self.sense_amp_array = None
|
self.sense_amp_array = None
|
||||||
|
|
||||||
|
|
||||||
if self.col_addr_size > 0:
|
if self.col_addr_size > 0:
|
||||||
|
# RBLs dont get a col mux
|
||||||
self.column_mux_array = factory.create(module_type="column_mux_array",
|
self.column_mux_array = factory.create(module_type="column_mux_array",
|
||||||
columns=self.num_cols,
|
columns=self.num_cols,
|
||||||
word_size=self.word_size,
|
word_size=self.word_size,
|
||||||
|
|
@ -180,19 +219,20 @@ class port_data(design.design):
|
||||||
else:
|
else:
|
||||||
self.column_mux_array = None
|
self.column_mux_array = None
|
||||||
|
|
||||||
|
|
||||||
if self.port in self.write_ports:
|
if self.port in self.write_ports:
|
||||||
|
# RBLs dont get a write driver
|
||||||
self.write_driver_array = factory.create(module_type="write_driver_array",
|
self.write_driver_array = factory.create(module_type="write_driver_array",
|
||||||
columns=self.num_cols,
|
columns=self.num_cols,
|
||||||
word_size=self.word_size,
|
word_size=self.word_size,
|
||||||
write_size=self.write_size)
|
write_size=self.write_size,
|
||||||
|
num_spare_cols=self.num_spare_cols)
|
||||||
self.add_mod(self.write_driver_array)
|
self.add_mod(self.write_driver_array)
|
||||||
if self.write_size is not None:
|
if self.write_size is not None:
|
||||||
|
# RBLs don't get a write mask
|
||||||
self.write_mask_and_array = factory.create(module_type="write_mask_and_array",
|
self.write_mask_and_array = factory.create(module_type="write_mask_and_array",
|
||||||
columns=self.num_cols,
|
columns=self.num_cols,
|
||||||
word_size=self.word_size,
|
word_size=self.word_size,
|
||||||
write_size=self.write_size,
|
write_size=self.write_size)
|
||||||
port = self.port)
|
|
||||||
self.add_mod(self.write_mask_and_array)
|
self.add_mod(self.write_mask_and_array)
|
||||||
else:
|
else:
|
||||||
self.write_mask_and_array = None
|
self.write_mask_and_array = None
|
||||||
|
|
@ -210,17 +250,21 @@ class port_data(design.design):
|
||||||
else:
|
else:
|
||||||
self.num_col_addr_lines = 0
|
self.num_col_addr_lines = 0
|
||||||
|
|
||||||
|
|
||||||
# A space for wells or jogging m2 between modules
|
# A space for wells or jogging m2 between modules
|
||||||
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"),
|
||||||
3*self.m2_pitch)
|
3 * self.m2_pitch)
|
||||||
|
|
||||||
|
|
||||||
# create arrays of bitline and bitline_bar names for read, write, or all ports
|
# create arrays of bitline and bitline_bar names for read,
|
||||||
|
# write, or all ports
|
||||||
self.bitcell = factory.create(module_type="bitcell")
|
self.bitcell = factory.create(module_type="bitcell")
|
||||||
self.bl_names = self.bitcell.get_all_bl_names()
|
self.bl_names = self.bitcell.get_all_bl_names()
|
||||||
self.br_names = self.bitcell.get_all_br_names()
|
self.br_names = self.bitcell.get_all_br_names()
|
||||||
self.wl_names = self.bitcell.get_all_wl_names()
|
self.wl_names = self.bitcell.get_all_wl_names()
|
||||||
|
# used for bl/br names
|
||||||
|
self.precharge = factory.create(module_type="precharge",
|
||||||
|
bitcell_bl=self.bl_names[0],
|
||||||
|
bitcell_br=self.br_names[0])
|
||||||
|
|
||||||
def create_precharge_array(self):
|
def create_precharge_array(self):
|
||||||
""" Creating Precharge """
|
""" Creating Precharge """
|
||||||
|
|
@ -230,6 +274,8 @@ class port_data(design.design):
|
||||||
|
|
||||||
self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port),
|
self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port),
|
||||||
mod=self.precharge_array)
|
mod=self.precharge_array)
|
||||||
|
bl_name = self.get_bl_name(self.port)
|
||||||
|
br_name = self.get_br_name(self.port)
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
# Use left BLs for RBL
|
# Use left BLs for RBL
|
||||||
|
|
@ -237,8 +283,13 @@ class port_data(design.design):
|
||||||
temp.append("rbl_bl")
|
temp.append("rbl_bl")
|
||||||
temp.append("rbl_br")
|
temp.append("rbl_br")
|
||||||
for bit in range(self.num_cols):
|
for bit in range(self.num_cols):
|
||||||
temp.append(self.bl_names[self.port]+"_{0}".format(bit))
|
temp.append("{0}_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port]+"_{0}".format(bit))
|
temp.append("{0}_{1}".format(br_name, bit))
|
||||||
|
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||||
|
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||||
|
|
||||||
# Use right BLs for RBL
|
# Use right BLs for RBL
|
||||||
if self.port==1:
|
if self.port==1:
|
||||||
temp.append("rbl_bl")
|
temp.append("rbl_bl")
|
||||||
|
|
@ -246,32 +297,32 @@ class port_data(design.design):
|
||||||
temp.extend(["p_en_bar", "vdd"])
|
temp.extend(["p_en_bar", "vdd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
def place_precharge_array(self, offset):
|
def place_precharge_array(self, offset):
|
||||||
""" Placing Precharge """
|
""" Placing Precharge """
|
||||||
|
|
||||||
self.precharge_array_inst.place(offset=offset, mirror="MX")
|
self.precharge_array_inst.place(offset=offset, mirror="MX")
|
||||||
|
|
||||||
|
|
||||||
def create_column_mux_array(self):
|
def create_column_mux_array(self):
|
||||||
""" Creating Column Mux when words_per_row > 1 . """
|
""" Creating Column Mux when words_per_row > 1 . """
|
||||||
|
|
||||||
self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port),
|
self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port),
|
||||||
mod=self.column_mux_array)
|
mod=self.column_mux_array)
|
||||||
|
|
||||||
|
bl_name = self.get_bl_name(self.port)
|
||||||
|
br_name = self.get_br_name(self.port)
|
||||||
temp = []
|
temp = []
|
||||||
for col in range(self.num_cols):
|
for col in range(self.num_cols):
|
||||||
temp.append(self.bl_names[self.port]+"_{0}".format(col))
|
temp.append("{0}_{1}".format(bl_name, col))
|
||||||
temp.append(self.br_names[self.port]+"_{0}".format(col))
|
temp.append("{0}_{1}".format(br_name, col))
|
||||||
for word in range(self.words_per_row):
|
for word in range(self.words_per_row):
|
||||||
temp.append("sel_{}".format(word))
|
temp.append("sel_{}".format(word))
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
temp.append(self.bl_names[self.port]+"_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port]+"_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||||
|
|
||||||
temp.append("gnd")
|
temp.append("gnd")
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
def place_column_mux_array(self, offset):
|
def place_column_mux_array(self, offset):
|
||||||
""" Placing Column Mux when words_per_row > 1 . """
|
""" Placing Column Mux when words_per_row > 1 . """
|
||||||
if self.col_addr_size == 0:
|
if self.col_addr_size == 0:
|
||||||
|
|
@ -279,63 +330,79 @@ class port_data(design.design):
|
||||||
|
|
||||||
self.column_mux_array_inst.place(offset=offset, mirror="MX")
|
self.column_mux_array_inst.place(offset=offset, mirror="MX")
|
||||||
|
|
||||||
|
|
||||||
def create_sense_amp_array(self):
|
def create_sense_amp_array(self):
|
||||||
""" Creating Sense amp """
|
""" Creating Sense amp """
|
||||||
self.sense_amp_array_inst = self.add_inst(name="sense_amp_array{}".format(self.port),
|
self.sense_amp_array_inst = self.add_inst(name="sense_amp_array{}".format(self.port),
|
||||||
mod=self.sense_amp_array)
|
mod=self.sense_amp_array)
|
||||||
|
|
||||||
|
bl_name = self.get_bl_name(self.port)
|
||||||
|
br_name = self.get_br_name(self.port)
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
temp.append("dout_{}".format(bit))
|
temp.append("dout_{}".format(bit))
|
||||||
if self.words_per_row == 1:
|
if self.words_per_row == 1:
|
||||||
temp.append(self.bl_names[self.port]+"_{0}".format(bit))
|
temp.append("{0}_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port]+"_{0}".format(bit))
|
temp.append("{0}_{1}".format(br_name, bit))
|
||||||
else:
|
else:
|
||||||
temp.append(self.bl_names[self.port]+"_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port]+"_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||||
|
|
||||||
temp.extend(["s_en", "vdd", "gnd"])
|
for bit in range(self.num_spare_cols):
|
||||||
|
temp.append("dout_{}".format(self.word_size + bit))
|
||||||
|
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||||
|
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||||
|
|
||||||
|
temp.append("s_en")
|
||||||
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
def place_sense_amp_array(self, offset):
|
def place_sense_amp_array(self, offset):
|
||||||
""" Placing Sense amp """
|
""" Placing Sense amp """
|
||||||
self.sense_amp_array_inst.place(offset=offset, mirror="MX")
|
self.sense_amp_array_inst.place(offset=offset, mirror="MX")
|
||||||
|
|
||||||
|
|
||||||
def create_write_driver_array(self):
|
def create_write_driver_array(self):
|
||||||
""" Creating Write Driver """
|
""" Creating Write Driver """
|
||||||
self.write_driver_array_inst = self.add_inst(name="write_driver_array{}".format(self.port),
|
self.write_driver_array_inst = self.add_inst(name="write_driver_array{}".format(self.port),
|
||||||
mod=self.write_driver_array)
|
mod=self.write_driver_array)
|
||||||
|
bl_name = self.get_bl_name(self.port)
|
||||||
|
br_name = self.get_br_name(self.port)
|
||||||
|
|
||||||
temp = []
|
temp = []
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
temp.append("din_{}".format(bit))
|
temp.append("din_{}".format(bit))
|
||||||
|
|
||||||
for bit in range(self.word_size):
|
for bit in range(self.word_size):
|
||||||
if (self.words_per_row == 1):
|
if (self.words_per_row == 1):
|
||||||
temp.append(self.bl_names[self.port] + "_{0}".format(bit))
|
temp.append("{0}_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port] + "_{0}".format(bit))
|
temp.append("{0}_{1}".format(br_name, bit))
|
||||||
else:
|
else:
|
||||||
temp.append(self.bl_names[self.port] + "_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||||
temp.append(self.br_names[self.port] + "_out_{0}".format(bit))
|
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||||
|
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||||
|
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||||
|
|
||||||
if self.write_size is not None:
|
if self.write_size is not None:
|
||||||
for i in range(self.num_wmasks):
|
for i in range(self.num_wmasks):
|
||||||
temp.append("wdriver_sel_{}".format(i))
|
temp.append("wdriver_sel_{}".format(i))
|
||||||
|
for i in range(self.num_spare_cols):
|
||||||
|
temp.append("bank_spare_wen{}".format(i))
|
||||||
|
|
||||||
|
elif self.num_spare_cols and not self.write_size:
|
||||||
|
temp.append("w_en")
|
||||||
|
for i in range(self.num_spare_cols):
|
||||||
|
temp.append("bank_spare_wen{}".format(i))
|
||||||
else:
|
else:
|
||||||
temp.append("w_en")
|
temp.append("w_en")
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
|
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
def place_write_driver_array(self, offset):
|
def place_write_driver_array(self, offset):
|
||||||
""" Placing Write Driver """
|
""" Placing Write Driver """
|
||||||
self.write_driver_array_inst.place(offset=offset, mirror="MX")
|
self.write_driver_array_inst.place(offset=offset, mirror="MX")
|
||||||
|
|
||||||
|
|
||||||
def create_write_mask_and_array(self):
|
def create_write_mask_and_array(self):
|
||||||
""" Creating Write Mask AND Array """
|
""" Creating Write Mask AND Array """
|
||||||
self.write_mask_and_array_inst = self.add_inst(name="write_mask_and_array{}".format(self.port),
|
self.write_mask_and_array_inst = self.add_inst(name="write_mask_and_array{}".format(self.port),
|
||||||
|
|
@ -350,12 +417,10 @@ class port_data(design.design):
|
||||||
temp.extend(["vdd", "gnd"])
|
temp.extend(["vdd", "gnd"])
|
||||||
self.connect_inst(temp)
|
self.connect_inst(temp)
|
||||||
|
|
||||||
|
|
||||||
def place_write_mask_and_array(self, offset):
|
def place_write_mask_and_array(self, offset):
|
||||||
""" Placing Write Mask AND array """
|
""" Placing Write Mask AND array """
|
||||||
self.write_mask_and_array_inst.place(offset=offset, mirror="MX")
|
self.write_mask_and_array_inst.place(offset=offset, mirror="MX")
|
||||||
|
|
||||||
|
|
||||||
def compute_instance_offsets(self):
|
def compute_instance_offsets(self):
|
||||||
"""
|
"""
|
||||||
Compute the empty instance offsets for port0 and port1 (if needed)
|
Compute the empty instance offsets for port0 and port1 (if needed)
|
||||||
|
|
@ -410,28 +475,24 @@ class port_data(design.design):
|
||||||
if self.column_mux_offset:
|
if self.column_mux_offset:
|
||||||
self.place_column_mux_array(self.column_mux_offset)
|
self.place_column_mux_array(self.column_mux_offset)
|
||||||
|
|
||||||
|
|
||||||
def route_sense_amp_out(self, port):
|
def route_sense_amp_out(self, port):
|
||||||
""" Add pins for the sense amp output """
|
""" Add pins for the sense amp output """
|
||||||
|
for bit in range(self.word_size + self.num_spare_cols):
|
||||||
for bit in range(self.word_size):
|
|
||||||
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit))
|
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit))
|
||||||
self.add_layout_pin_rect_center(text="dout_{0}".format(bit),
|
self.add_layout_pin_rect_center(text="dout_{0}".format(bit),
|
||||||
layer=data_pin.layer,
|
layer=data_pin.layer,
|
||||||
offset=data_pin.center(),
|
offset=data_pin.center(),
|
||||||
height=data_pin.height(),
|
height=data_pin.height(),
|
||||||
width=data_pin.width())
|
width=data_pin.width())
|
||||||
|
|
||||||
|
|
||||||
def route_write_driver_in(self, port):
|
def route_write_driver_in(self, port):
|
||||||
""" Connecting write driver """
|
""" Connecting write driver """
|
||||||
|
|
||||||
for row in range(self.word_size):
|
for row in range(self.word_size + self.num_spare_cols):
|
||||||
data_name = "data_{}".format(row)
|
data_name = "data_{}".format(row)
|
||||||
din_name = "din_{}".format(row)
|
din_name = "din_{}".format(row)
|
||||||
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||||
|
|
||||||
|
|
||||||
def route_write_mask_and_array_in(self, port):
|
def route_write_mask_and_array_in(self, port):
|
||||||
""" Add pins for the write mask and array input """
|
""" Add pins for the write mask and array input """
|
||||||
|
|
||||||
|
|
@ -440,47 +501,38 @@ class port_data(design.design):
|
||||||
bank_wmask_name = "bank_wmask_{}".format(bit)
|
bank_wmask_name = "bank_wmask_{}".format(bit)
|
||||||
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
|
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
|
||||||
|
|
||||||
|
def route_write_mask_and_array_to_write_driver(self, port):
|
||||||
|
"""
|
||||||
|
Routing of wdriver_sel_{} between write mask AND array and
|
||||||
|
write driver array. Adds layout pin for write
|
||||||
|
mask AND array output and via for write driver enable
|
||||||
|
"""
|
||||||
|
|
||||||
def route_write_mask_and_array_to_write_driver(self,port):
|
wmask_inst = self.write_mask_and_array_inst
|
||||||
""" Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write
|
wdriver_inst = self.write_driver_array_inst
|
||||||
mask AND array output and via for write driver enable """
|
|
||||||
|
|
||||||
inst1 = self.write_mask_and_array_inst
|
|
||||||
inst2 = self.write_driver_array_inst
|
|
||||||
|
|
||||||
loc = 0
|
|
||||||
for bit in range(self.num_wmasks):
|
for bit in range(self.num_wmasks):
|
||||||
# Bring write mask AND array output pin to port data level
|
# Bring write mask AND array output pin to port data level
|
||||||
self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
||||||
|
|
||||||
wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit))
|
wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit))
|
||||||
wdriver_en_pin = inst2.get_pin("en_{0}".format(bit))
|
wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit))
|
||||||
|
|
||||||
# The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the
|
wmask_pos = wmask_out_pin.center()
|
||||||
# the wdriver_sel_{} pin in the write driver AND array.
|
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
|
||||||
if bit == 0:
|
mid_pos = vector(wdriver_pos.x, wmask_pos.y)
|
||||||
# When the write mask output pin is right of the bitline, the target is found
|
|
||||||
while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()):
|
|
||||||
loc += 1
|
|
||||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
|
||||||
debug.check(loc<=self.num_wmasks,"Couldn't route the write mask select.")
|
|
||||||
else:
|
|
||||||
# Stride by the write size rather than finding the next pin to the right
|
|
||||||
loc += self.write_size
|
|
||||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
|
||||||
|
|
||||||
|
|
||||||
beg_pos = wmask_out_pin.center()
|
|
||||||
middle_pos = vector(length,wmask_out_pin.cy())
|
|
||||||
end_pos = vector(length, wdriver_en_pin.cy())
|
|
||||||
|
|
||||||
|
# Add driver on mask output
|
||||||
|
self.add_via_stack_center(from_layer=wmask_out_pin.layer,
|
||||||
|
to_layer="m1",
|
||||||
|
offset=wmask_pos)
|
||||||
# Add via for the write driver array's enable input
|
# Add via for the write driver array's enable input
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
self.add_via_stack_center(from_layer=wdriver_en_pin.layer,
|
||||||
offset=end_pos)
|
to_layer="m2",
|
||||||
|
offset=wdriver_pos)
|
||||||
|
|
||||||
# Route between write mask AND array and write driver array
|
# Route between write mask AND array and write driver array
|
||||||
self.add_wire(("metal1","via1","metal2"), [beg_pos, middle_pos, end_pos])
|
self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])
|
||||||
|
|
||||||
|
|
||||||
def route_column_mux_to_precharge_array(self, port):
|
def route_column_mux_to_precharge_array(self, port):
|
||||||
""" Routing of BL and BR between col mux and precharge array """
|
""" Routing of BL and BR between col mux and precharge array """
|
||||||
|
|
@ -488,14 +540,13 @@ class port_data(design.design):
|
||||||
# Only do this if we have a column mux!
|
# Only do this if we have a column mux!
|
||||||
if self.col_addr_size==0:
|
if self.col_addr_size==0:
|
||||||
return
|
return
|
||||||
|
|
||||||
inst1 = self.column_mux_array_inst
|
|
||||||
inst2 = self.precharge_array_inst
|
|
||||||
if self.port==0:
|
|
||||||
self.connect_bitlines(inst1, inst2, self.num_cols, inst2_start_bit=1)
|
|
||||||
else:
|
|
||||||
self.connect_bitlines(inst1, inst2, self.num_cols)
|
|
||||||
|
|
||||||
|
start_bit = 1 if self.port == 0 else 0
|
||||||
|
|
||||||
|
self.connect_bitlines(inst1=self.column_mux_array_inst,
|
||||||
|
inst2=self.precharge_array_inst,
|
||||||
|
num_bits=self.num_cols,
|
||||||
|
inst2_start_bit=start_bit)
|
||||||
|
|
||||||
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
||||||
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
||||||
|
|
@ -504,47 +555,109 @@ class port_data(design.design):
|
||||||
if self.col_addr_size>0:
|
if self.col_addr_size>0:
|
||||||
# Sense amp is connected to the col mux
|
# Sense amp is connected to the col mux
|
||||||
inst1 = self.column_mux_array_inst
|
inst1 = self.column_mux_array_inst
|
||||||
inst1_bl_name = "bl_out_{}"
|
inst1_bls_templ = "{inst}_out_{bit}"
|
||||||
inst1_br_name = "br_out_{}"
|
|
||||||
start_bit = 0
|
start_bit = 0
|
||||||
else:
|
else:
|
||||||
# Sense amp is directly connected to the precharge array
|
# Sense amp is directly connected to the precharge array
|
||||||
inst1 = self.precharge_array_inst
|
inst1 = self.precharge_array_inst
|
||||||
inst1_bl_name = "bl_{}"
|
inst1_bls_templ="{inst}_{bit}"
|
||||||
inst1_br_name = "br_{}"
|
|
||||||
if self.port==0:
|
if self.port==0:
|
||||||
start_bit=1
|
start_bit=1
|
||||||
else:
|
else:
|
||||||
start_bit=0
|
start_bit=0
|
||||||
|
|
||||||
|
# spare cols connected to precharge array since they are read independently
|
||||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
if self.num_spare_cols and self.col_addr_size>0:
|
||||||
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit)
|
if self.port==0:
|
||||||
|
off = 1
|
||||||
|
else:
|
||||||
|
off = 0
|
||||||
|
|
||||||
|
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
|
||||||
|
inst1_bls_template="{inst}_out_{bit}",
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.word_size,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
|
||||||
|
self.channel_route_bitlines(inst1=self.precharge_array_inst,
|
||||||
|
inst1_bls_template="{inst}_{bit}",
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.num_spare_cols,
|
||||||
|
inst1_start_bit=self.num_cols + off,
|
||||||
|
inst2_start_bit=self.word_size)
|
||||||
|
|
||||||
|
# This could be a channel route, but in some techs the bitlines
|
||||||
|
# are too close together.
|
||||||
|
elif OPTS.tech_name == "sky130":
|
||||||
|
self.connect_bitlines(inst1=inst1,
|
||||||
|
inst1_bls_template=inst1_bls_templ,
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.word_size,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
else:
|
||||||
|
self.channel_route_bitlines(inst1=inst1,
|
||||||
|
inst1_bls_template=inst1_bls_templ,
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.word_size + self.num_spare_cols,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
|
||||||
def route_write_driver_to_column_mux_or_precharge_array(self, port):
|
def route_write_driver_to_column_mux_or_precharge_array(self, port):
|
||||||
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
||||||
inst2 = self.write_driver_array_inst
|
inst2 = self.write_driver_array_inst
|
||||||
|
|
||||||
if self.col_addr_size>0:
|
if self.col_addr_size>0:
|
||||||
# Write driver is connected to the col mux
|
# Write driver is connected to the col mux
|
||||||
inst1 = self.column_mux_array_inst
|
inst1 = self.column_mux_array_inst
|
||||||
inst1_bl_name = "bl_out_{}"
|
inst1_bls_templ = "{inst}_out_{bit}"
|
||||||
inst1_br_name = "br_out_{}"
|
|
||||||
start_bit = 0
|
start_bit = 0
|
||||||
else:
|
else:
|
||||||
# Sense amp is directly connected to the precharge array
|
# Sense amp is directly connected to the precharge array
|
||||||
inst1 = self.precharge_array_inst
|
inst1 = self.precharge_array_inst
|
||||||
inst1_bl_name = "bl_{}"
|
inst1_bls_templ="{inst}_{bit}"
|
||||||
inst1_br_name = "br_{}"
|
|
||||||
if self.port==0:
|
if self.port==0:
|
||||||
start_bit=1
|
start_bit=1
|
||||||
else:
|
else:
|
||||||
start_bit=0
|
start_bit=0
|
||||||
|
|
||||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
|
||||||
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit)
|
|
||||||
|
|
||||||
|
if self.port==0:
|
||||||
|
off = 1
|
||||||
|
else:
|
||||||
|
off = 0
|
||||||
|
|
||||||
|
# Channel route spare columns' bitlines
|
||||||
|
if self.num_spare_cols and self.col_addr_size>0:
|
||||||
|
if self.port==0:
|
||||||
|
off = 1
|
||||||
|
else:
|
||||||
|
off = 0
|
||||||
|
|
||||||
|
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
|
||||||
|
inst1_bls_template="{inst}_out_{bit}",
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.word_size,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
|
||||||
|
self.channel_route_bitlines(inst1=self.precharge_array_inst,
|
||||||
|
inst1_bls_template="{inst}_{bit}",
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.num_spare_cols,
|
||||||
|
inst1_start_bit=self.num_cols + off,
|
||||||
|
inst2_start_bit=self.word_size)
|
||||||
|
|
||||||
|
# This could be a channel route, but in some techs the bitlines
|
||||||
|
# are too close together.
|
||||||
|
elif OPTS.tech_name == "sky130":
|
||||||
|
self.connect_bitlines(inst1=inst1, inst2=inst2,
|
||||||
|
num_bits=self.word_size,
|
||||||
|
inst1_bls_template=inst1_bls_templ,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
else:
|
||||||
|
self.channel_route_bitlines(inst1=inst1, inst2=inst2,
|
||||||
|
num_bits=self.word_size+self.num_spare_cols,
|
||||||
|
inst1_bls_template=inst1_bls_templ,
|
||||||
|
inst1_start_bit=start_bit)
|
||||||
|
|
||||||
|
|
||||||
def route_write_driver_to_sense_amp(self, port):
|
def route_write_driver_to_sense_amp(self, port):
|
||||||
""" Routing of BL and BR between write driver and sense amp """
|
""" Routing of BL and BR between write driver and sense amp """
|
||||||
|
|
@ -552,10 +665,11 @@ class port_data(design.design):
|
||||||
inst1 = self.write_driver_array_inst
|
inst1 = self.write_driver_array_inst
|
||||||
inst2 = self.sense_amp_array_inst
|
inst2 = self.sense_amp_array_inst
|
||||||
|
|
||||||
# These should be pitch matched in the cell library,
|
# This could be a channel route, but in some techs the bitlines
|
||||||
# but just in case, do a channel route.
|
# are too close together.
|
||||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size)
|
self.connect_bitlines(inst1=inst1,
|
||||||
|
inst2=inst2,
|
||||||
|
num_bits=self.word_size + self.num_spare_cols)
|
||||||
|
|
||||||
def route_bitline_pins(self):
|
def route_bitline_pins(self):
|
||||||
""" Add the bitline pins for the given port """
|
""" Add the bitline pins for the given port """
|
||||||
|
|
@ -566,16 +680,32 @@ class port_data(design.design):
|
||||||
self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br")
|
self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br")
|
||||||
bit_offset=1
|
bit_offset=1
|
||||||
elif self.port==1:
|
elif self.port==1:
|
||||||
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl")
|
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols + self.num_spare_cols), "rbl_bl")
|
||||||
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br")
|
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols + self.num_spare_cols), "rbl_br")
|
||||||
bit_offset=0
|
bit_offset=0
|
||||||
else:
|
else:
|
||||||
bit_offset=0
|
bit_offset=0
|
||||||
|
|
||||||
for bit in range(self.num_cols):
|
for bit in range(self.num_cols):
|
||||||
if self.precharge_array_inst:
|
if self.precharge_array_inst:
|
||||||
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(bit+bit_offset), "bl_{}".format(bit))
|
self.copy_layout_pin(self.precharge_array_inst,
|
||||||
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(bit+bit_offset), "br_{}".format(bit))
|
"bl_{}".format(bit + bit_offset),
|
||||||
|
"bl_{}".format(bit))
|
||||||
|
self.copy_layout_pin(self.precharge_array_inst,
|
||||||
|
"br_{}".format(bit + bit_offset),
|
||||||
|
"br_{}".format(bit))
|
||||||
|
else:
|
||||||
|
debug.error("Didn't find precharge array.")
|
||||||
|
|
||||||
|
# Copy bitlines of spare columns
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
if self.precharge_array_inst:
|
||||||
|
self.copy_layout_pin(self.precharge_array_inst,
|
||||||
|
"bl_{}".format(self.num_cols + bit + bit_offset),
|
||||||
|
"sparebl_{}".format(bit))
|
||||||
|
self.copy_layout_pin(self.precharge_array_inst,
|
||||||
|
"br_{}".format(self.num_cols + bit + bit_offset),
|
||||||
|
"sparebr_{}".format(bit))
|
||||||
else:
|
else:
|
||||||
debug.error("Didn't find precharge array.")
|
debug.error("Didn't find precharge array.")
|
||||||
|
|
||||||
|
|
@ -589,79 +719,119 @@ class port_data(design.design):
|
||||||
for pin_name in sel_names:
|
for pin_name in sel_names:
|
||||||
self.copy_layout_pin(self.column_mux_array_inst, pin_name)
|
self.copy_layout_pin(self.column_mux_array_inst, pin_name)
|
||||||
if self.sense_amp_array_inst:
|
if self.sense_amp_array_inst:
|
||||||
self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en")
|
self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en")
|
||||||
if self.write_driver_array_inst:
|
if self.write_driver_array_inst:
|
||||||
if self.write_mask_and_array_inst:
|
if self.write_mask_and_array_inst:
|
||||||
for bit in range(self.num_wmasks):
|
for bit in range(self.num_wmasks):
|
||||||
# Add write driver's en_{} pins
|
# Add write driver's en_{} pins
|
||||||
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit))
|
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit))
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
# Add spare columns' en_{} pins
|
||||||
|
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + self.num_wmasks), "bank_spare_wen{}".format(bit))
|
||||||
|
elif self.num_spare_cols and not self.write_mask_and_array_inst:
|
||||||
|
self.copy_layout_pin(self.write_driver_array_inst, "en_0", "w_en")
|
||||||
|
for bit in range(self.num_spare_cols):
|
||||||
|
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + 1), "bank_spare_wen{}".format(bit))
|
||||||
else:
|
else:
|
||||||
self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en")
|
self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en")
|
||||||
if self.write_mask_and_array_inst:
|
if self.write_mask_and_array_inst:
|
||||||
self.copy_layout_pin(self.write_mask_and_array_inst, "en", "w_en")
|
self.copy_layout_pin(self.write_mask_and_array_inst, "en", "w_en")
|
||||||
|
|
||||||
|
def _group_bitline_instances(self, inst1, inst2, num_bits,
|
||||||
|
inst1_bls_template,
|
||||||
def channel_route_bitlines(self, inst1, inst2, num_bits,
|
inst1_start_bit,
|
||||||
inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0,
|
inst2_bls_template,
|
||||||
inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0):
|
inst2_start_bit):
|
||||||
"""
|
"""
|
||||||
Route the bl and br of two modules using the channel router.
|
Groups all the parameters into a named tuple and seperates them into
|
||||||
|
top and bottom instances.
|
||||||
"""
|
"""
|
||||||
|
inst_group = namedtuple('InstanceGroup', ('inst', 'bls_template',
|
||||||
|
'bl_name', 'br_name', 'start_bit'))
|
||||||
|
|
||||||
|
inst1_group = inst_group(inst1, inst1_bls_template,
|
||||||
|
inst1.mod.get_bl_name(),
|
||||||
|
inst1.mod.get_br_name(),
|
||||||
|
inst1_start_bit)
|
||||||
|
inst2_group = inst_group(inst2, inst2_bls_template,
|
||||||
|
inst2.mod.get_bl_name(),
|
||||||
|
inst2.mod.get_br_name(),
|
||||||
|
inst2_start_bit)
|
||||||
# determine top and bottom automatically.
|
# determine top and bottom automatically.
|
||||||
# since they don't overlap, we can just check the bottom y coordinate.
|
# since they don't overlap, we can just check the bottom y coordinate.
|
||||||
if inst1.by() < inst2.by():
|
if inst1.by() < inst2.by():
|
||||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
bot_inst_group = inst1_group
|
||||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
top_inst_group = inst2_group
|
||||||
else:
|
else:
|
||||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
bot_inst_group = inst2_group
|
||||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
top_inst_group = inst1_group
|
||||||
|
|
||||||
|
return (bot_inst_group, top_inst_group)
|
||||||
|
|
||||||
|
def _get_bitline_pins(self, inst_group, bit):
|
||||||
|
"""
|
||||||
|
Extracts bl/br pins from an InstanceGroup based on the bit modifier.
|
||||||
|
"""
|
||||||
|
full_bl_name = inst_group.bls_template.format(
|
||||||
|
**{'inst': inst_group.bl_name,
|
||||||
|
'bit': inst_group.start_bit + bit}
|
||||||
|
)
|
||||||
|
full_br_name = inst_group.bls_template.format(
|
||||||
|
**{'inst': inst_group.br_name,
|
||||||
|
'bit': inst_group.start_bit + bit}
|
||||||
|
)
|
||||||
|
return (inst_group.inst.get_pin(full_bl_name),
|
||||||
|
inst_group.inst.get_pin(full_br_name))
|
||||||
|
|
||||||
|
def channel_route_bitlines(self, inst1, inst2, num_bits,
|
||||||
|
inst1_bls_template="{inst}_{bit}",
|
||||||
|
inst1_start_bit=0,
|
||||||
|
inst2_bls_template="{inst}_{bit}",
|
||||||
|
inst2_start_bit=0):
|
||||||
|
"""
|
||||||
|
Route the bl and br of two modules using the channel router.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
|
||||||
|
inst1_bls_template, inst1_start_bit,
|
||||||
|
inst2_bls_template, inst2_start_bit)
|
||||||
|
|
||||||
# Channel route each mux separately since we don't minimize the number
|
# Channel route each mux separately since we don't minimize the number
|
||||||
# of tracks in teh channel router yet. If we did, we could route all the bits at once!
|
# of tracks in teh channel router yet. If we did, we could route all the bits at once!
|
||||||
offset = bottom_inst.ul() + vector(0,self.m1_pitch)
|
offset = bot_inst_group.inst.ul() + vector(0, self.m1_nonpref_pitch)
|
||||||
for bit in range(num_bits):
|
for bit in range(num_bits):
|
||||||
bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit+bottom_start_bit)), bottom_inst.get_pin(bottom_br_name.format(bit+bottom_start_bit))]
|
bottom_names = self._get_bitline_pins(bot_inst_group, bit)
|
||||||
top_names = [top_inst.get_pin(top_bl_name.format(bit+top_start_bit)), top_inst.get_pin(top_br_name.format(bit+top_start_bit))]
|
top_names = self._get_bitline_pins(top_inst_group, bit)
|
||||||
|
|
||||||
route_map = list(zip(bottom_names, top_names))
|
route_map = list(zip(bottom_names, top_names))
|
||||||
self.create_horizontal_channel_route(route_map, offset)
|
self.create_horizontal_channel_route(route_map, offset, self.m1_stack)
|
||||||
|
|
||||||
|
|
||||||
def connect_bitlines(self, inst1, inst2, num_bits,
|
def connect_bitlines(self, inst1, inst2, num_bits,
|
||||||
inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0,
|
inst1_bls_template="{inst}_{bit}",
|
||||||
inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0):
|
inst1_start_bit=0,
|
||||||
|
inst2_bls_template="{inst}_{bit}",
|
||||||
|
inst2_start_bit=0):
|
||||||
"""
|
"""
|
||||||
Connect the bl and br of two modules.
|
Connect the bl and br of two modules.
|
||||||
This assumes that they have sufficient space to create a jog
|
This assumes that they have sufficient space to create a jog
|
||||||
in the middle between the two modules (if needed).
|
in the middle between the two modules (if needed).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# determine top and bottom automatically.
|
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
|
||||||
# since they don't overlap, we can just check the bottom y coordinate.
|
inst1_bls_template, inst1_start_bit,
|
||||||
if inst1.by() < inst2.by():
|
inst2_bls_template, inst2_start_bit)
|
||||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
|
||||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
|
||||||
else:
|
|
||||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
|
||||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
|
||||||
|
|
||||||
for col in range(num_bits):
|
for col in range(num_bits):
|
||||||
bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col+bottom_start_bit)).uc()
|
bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col)
|
||||||
bottom_br = bottom_inst.get_pin(bottom_br_name.format(col+bottom_start_bit)).uc()
|
top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col)
|
||||||
top_bl = top_inst.get_pin(top_bl_name.format(col+top_start_bit)).bc()
|
bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc()
|
||||||
top_br = top_inst.get_pin(top_br_name.format(col+top_start_bit)).bc()
|
top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc()
|
||||||
|
|
||||||
|
layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer))
|
||||||
|
self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch)
|
||||||
|
self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch)
|
||||||
|
|
||||||
yoffset = 0.5*(top_bl.y+bottom_bl.y)
|
|
||||||
self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset),
|
|
||||||
vector(top_bl.x,yoffset), top_bl])
|
|
||||||
self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset),
|
|
||||||
vector(top_br.x,yoffset), top_br])
|
|
||||||
|
|
||||||
def graph_exclude_precharge(self):
|
def graph_exclude_precharge(self):
|
||||||
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
|
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
|
||||||
if self.precharge_array_inst:
|
if self.precharge_array_inst:
|
||||||
self.graph_inst_exclude.add(self.precharge_array_inst)
|
self.graph_inst_exclude.add(self.precharge_array_inst)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@
|
||||||
#
|
#
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc
|
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
from tech import layer
|
||||||
|
|
||||||
|
|
||||||
class precharge_array(design.design):
|
class precharge_array(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -18,7 +19,7 @@ class precharge_array(design.design):
|
||||||
of bit line columns, height is the height of the bit-cell array.
|
of bit line columns, height is the height of the bit-cell array.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"):
|
def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br))
|
self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br))
|
||||||
|
|
@ -27,11 +28,25 @@ class precharge_array(design.design):
|
||||||
self.size = size
|
self.size = size
|
||||||
self.bitcell_bl = bitcell_bl
|
self.bitcell_bl = bitcell_bl
|
||||||
self.bitcell_br = bitcell_br
|
self.bitcell_br = bitcell_br
|
||||||
|
self.column_offset = column_offset
|
||||||
|
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
self.en_bar_layer = "m3"
|
||||||
|
else:
|
||||||
|
self.en_bar_layer = "m1"
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
def get_bl_name(self):
|
||||||
|
bl_name = self.pc_cell.get_bl_names()
|
||||||
|
return bl_name
|
||||||
|
|
||||||
|
def get_br_name(self):
|
||||||
|
br_name = self.pc_cell.get_br_names()
|
||||||
|
return br_name
|
||||||
|
|
||||||
def add_pins(self):
|
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):
|
||||||
|
|
@ -60,38 +75,29 @@ class precharge_array(design.design):
|
||||||
size=self.size,
|
size=self.size,
|
||||||
bitcell_bl=self.bitcell_bl,
|
bitcell_bl=self.bitcell_bl,
|
||||||
bitcell_br=self.bitcell_br)
|
bitcell_br=self.bitcell_br)
|
||||||
|
|
||||||
|
|
||||||
self.add_mod(self.pc_cell)
|
self.add_mod(self.pc_cell)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
|
|
||||||
self.add_layout_pin(text="en_bar",
|
en_pin = self.pc_cell.get_pin("en_bar")
|
||||||
layer="metal1",
|
start_offset = en_pin.lc().scale(0, 1)
|
||||||
offset=self.pc_cell.get_pin("en_bar").ll(),
|
end_offset = start_offset + vector(self.width, 0)
|
||||||
width=self.width,
|
self.add_layout_pin_segment_center(text="en_bar",
|
||||||
height=drc("minwidth_metal1"))
|
layer=self.en_bar_layer,
|
||||||
|
start=start_offset,
|
||||||
|
end=end_offset)
|
||||||
|
|
||||||
for inst in self.local_insts:
|
for inst in self.local_insts:
|
||||||
|
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||||
|
to_layer=self.en_bar_layer,
|
||||||
|
offset=inst.get_pin("en_bar").center())
|
||||||
self.copy_layout_pin(inst, "vdd")
|
self.copy_layout_pin(inst, "vdd")
|
||||||
|
|
||||||
for i in range(len(self.local_insts)):
|
for i in range(len(self.local_insts)):
|
||||||
inst = self.local_insts[i]
|
inst = self.local_insts[i]
|
||||||
bl_pin = inst.get_pin("bl")
|
self.copy_layout_pin(inst, "bl", "bl_{0}".format(i))
|
||||||
self.add_layout_pin(text="bl_{0}".format(i),
|
self.copy_layout_pin(inst, "br", "br_{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 create_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 = []
|
||||||
|
|
@ -104,16 +110,28 @@ class precharge_array(design.design):
|
||||||
self.local_insts.append(inst)
|
self.local_insts.append(inst)
|
||||||
self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en_bar", "vdd"])
|
self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en_bar", "vdd"])
|
||||||
|
|
||||||
|
|
||||||
def place_insts(self):
|
def place_insts(self):
|
||||||
""" Places precharge array by horizontally tiling the precharge cell"""
|
""" Places precharge array by horizontally tiling the precharge cell"""
|
||||||
|
from tech import cell_properties
|
||||||
|
xoffset = 0
|
||||||
for i in range(self.columns):
|
for i in range(self.columns):
|
||||||
offset = vector(self.pc_cell.width * i, 0)
|
tempx = xoffset
|
||||||
self.local_insts[i].place(offset)
|
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||||
|
mirror = "MY"
|
||||||
|
tempx = tempx + self.pc_cell.width
|
||||||
|
else:
|
||||||
|
mirror = ""
|
||||||
|
|
||||||
|
offset = vector(tempx, 0)
|
||||||
|
self.local_insts[i].place(offset=offset, mirror=mirror)
|
||||||
|
xoffset = xoffset + self.pc_cell.width
|
||||||
|
|
||||||
def get_en_cin(self):
|
def get_en_cin(self):
|
||||||
"""Get the relative capacitance of all the clk connections in the precharge array"""
|
"""
|
||||||
#Assume single port
|
Get the relative capacitance of all the clk connections
|
||||||
|
in the precharge array
|
||||||
|
"""
|
||||||
|
# Assume single port
|
||||||
precharge_en_cin = self.pc_cell.get_en_cin()
|
precharge_en_cin = self.pc_cell.get_en_cin()
|
||||||
return precharge_en_cin*self.columns
|
return precharge_en_cin * self.columns
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
# See LICENSE for licensing information.
|
# See LICENSE for licensing information.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
|
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc, spice
|
from tech import drc, spice, cell_properties
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
import logical_effort
|
|
||||||
import bitcell_array
|
|
||||||
import replica_column
|
|
||||||
import dummy_array
|
|
||||||
|
|
||||||
class replica_bitcell_array(design.design):
|
class replica_bitcell_array(design.design):
|
||||||
"""
|
"""
|
||||||
|
|
@ -35,22 +32,23 @@ class replica_bitcell_array(design.design):
|
||||||
self.right_rbl = right_rbl
|
self.right_rbl = right_rbl
|
||||||
self.bitcell_ports = bitcell_ports
|
self.bitcell_ports = bitcell_ports
|
||||||
|
|
||||||
debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.")
|
debug.check(left_rbl + right_rbl == len(self.all_ports),
|
||||||
debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.")
|
"Invalid number of RBLs for port configuration.")
|
||||||
|
debug.check(left_rbl + right_rbl == len(self.bitcell_ports),
|
||||||
|
"Bitcell ports must match total RBLs.")
|
||||||
|
|
||||||
# Two dummy rows/cols plus replica for each port
|
# Two dummy rows/cols plus replica for each port
|
||||||
self.extra_rows = 2 + left_rbl + right_rbl
|
self.extra_rows = 2 + left_rbl + right_rbl
|
||||||
self.extra_cols = 2 + left_rbl + right_rbl
|
self.extra_cols = 2 + left_rbl + right_rbl
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
# We don't offset this because we need to align
|
# We don't offset this because we need to align
|
||||||
# the replica bitcell in the control logic
|
# the replica bitcell in the control logic
|
||||||
#self.offset_all_coordinates()
|
# self.offset_all_coordinates()
|
||||||
|
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
""" Create and connect the netlist """
|
""" Create and connect the netlist """
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
|
|
@ -58,15 +56,15 @@ class replica_bitcell_array(design.design):
|
||||||
self.create_instances()
|
self.create_instances()
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
""" Array and dummy/replica columns
|
""" Array and dummy/replica columns
|
||||||
|
|
||||||
d or D = dummy cell (caps to distinguish grouping)
|
d or D = dummy cell (caps to distinguish grouping)
|
||||||
r or R = replica cell (caps to distinguish grouping)
|
r or R = replica cell (caps to distinguish grouping)
|
||||||
b or B = bitcell
|
b or B = bitcell
|
||||||
replica columns 1
|
replica columns 1
|
||||||
v v
|
v v
|
||||||
bdDDDDDDDDDDDDDDdb <- Dummy row
|
bdDDDDDDDDDDDDDDdb <- Dummy row
|
||||||
bdDDDDDDDDDDDDDDrb <- Dummy row
|
bdDDDDDDDDDDDDDDrb <- Dummy row
|
||||||
br--------------rb
|
br--------------rb
|
||||||
br| Array |rb
|
br| Array |rb
|
||||||
br| row x col |rb
|
br| row x col |rb
|
||||||
|
|
@ -86,71 +84,109 @@ class replica_bitcell_array(design.design):
|
||||||
|
|
||||||
# Bitcell array
|
# Bitcell array
|
||||||
self.bitcell_array = factory.create(module_type="bitcell_array",
|
self.bitcell_array = factory.create(module_type="bitcell_array",
|
||||||
|
column_offset=1 + self.left_rbl,
|
||||||
cols=self.column_size,
|
cols=self.column_size,
|
||||||
rows=self.row_size)
|
rows=self.row_size)
|
||||||
self.add_mod(self.bitcell_array)
|
self.add_mod(self.bitcell_array)
|
||||||
|
|
||||||
# Replica bitlines
|
# Replica bitlines
|
||||||
self.replica_columns = {}
|
self.replica_columns = {}
|
||||||
for bit in range(self.left_rbl+self.right_rbl):
|
for bit in range(self.left_rbl + self.right_rbl):
|
||||||
|
# Creating left_rbl
|
||||||
if bit<self.left_rbl:
|
if bit<self.left_rbl:
|
||||||
replica_bit = bit+1
|
replica_bit = bit + 1
|
||||||
|
# dummy column
|
||||||
|
column_offset = self.left_rbl - bit
|
||||||
|
# Creating right_rbl
|
||||||
else:
|
else:
|
||||||
replica_bit = bit+self.row_size+1
|
replica_bit = bit + self.row_size + 1
|
||||||
|
# dummy column + replica column + bitcell colums
|
||||||
|
column_offset = self.left_rbl - bit + self.row_size
|
||||||
self.replica_columns[bit] = factory.create(module_type="replica_column",
|
self.replica_columns[bit] = factory.create(module_type="replica_column",
|
||||||
rows=self.row_size,
|
rows=self.row_size,
|
||||||
left_rbl=self.left_rbl,
|
left_rbl=self.left_rbl,
|
||||||
right_rbl=self.right_rbl,
|
right_rbl=self.right_rbl,
|
||||||
|
column_offset=column_offset,
|
||||||
replica_bit=replica_bit)
|
replica_bit=replica_bit)
|
||||||
self.add_mod(self.replica_columns[bit])
|
self.add_mod(self.replica_columns[bit])
|
||||||
|
|
||||||
# Dummy row
|
# Dummy row
|
||||||
self.dummy_row = factory.create(module_type="dummy_array",
|
self.dummy_row = factory.create(module_type="dummy_array",
|
||||||
cols=self.column_size,
|
cols=self.column_size,
|
||||||
rows=1,
|
rows=1,
|
||||||
|
# dummy column + left replica column
|
||||||
|
column_offset=1 + self.left_rbl,
|
||||||
mirror=0)
|
mirror=0)
|
||||||
self.add_mod(self.dummy_row)
|
self.add_mod(self.dummy_row)
|
||||||
|
|
||||||
# Dummy col (mirror starting at first if odd replica+dummy rows)
|
# If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
|
||||||
self.dummy_col = factory.create(module_type="dummy_array",
|
try:
|
||||||
cols=1,
|
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||||
rows=self.row_size + self.extra_rows,
|
except AttributeError:
|
||||||
mirror=(self.left_rbl+1)%2)
|
end_caps_enabled = False
|
||||||
self.add_mod(self.dummy_col)
|
|
||||||
|
# Dummy Row or Col Cap, depending on bitcell array properties
|
||||||
|
edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array")
|
||||||
|
|
||||||
|
self.edge_row = factory.create(module_type=edge_row_module_type,
|
||||||
|
cols=self.column_size,
|
||||||
|
rows=1,
|
||||||
|
# dummy column + left replica column(s)
|
||||||
|
column_offset=1 + self.left_rbl,
|
||||||
|
mirror=0)
|
||||||
|
self.add_mod(self.edge_row)
|
||||||
|
|
||||||
|
# Dummy Col or Row Cap, depending on bitcell array properties
|
||||||
|
edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array")
|
||||||
|
|
||||||
|
self.edge_col_left = factory.create(module_type=edge_col_module_type,
|
||||||
|
cols=1,
|
||||||
|
column_offset=0,
|
||||||
|
rows=self.row_size + self.extra_rows,
|
||||||
|
mirror=(self.left_rbl + 1) % 2)
|
||||||
|
self.add_mod(self.edge_col_left)
|
||||||
|
|
||||||
|
self.edge_col_right = factory.create(module_type=edge_col_module_type,
|
||||||
|
cols=1,
|
||||||
|
# dummy column
|
||||||
|
# + left replica column(s)
|
||||||
|
# + bitcell columns
|
||||||
|
# + right replica column(s)
|
||||||
|
column_offset = 1 + self.left_rbl + self.column_size + self.right_rbl,
|
||||||
|
rows=self.row_size + self.extra_rows,
|
||||||
|
mirror=(self.left_rbl + 1) %2)
|
||||||
|
self.add_mod(self.edge_col_right)
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
|
||||||
|
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names()
|
||||||
|
|
||||||
self.bitcell_array_wl_names = [x for x in self.bitcell_array.pins if x.startswith("w")]
|
# These are the non-indexed names
|
||||||
self.bitcell_array_bl_names = [x for x in self.bitcell_array.pins if x.startswith("b")]
|
self.dummy_cell_wl_names = ["dummy_" + x for x in self.cell.get_all_wl_names()]
|
||||||
|
self.dummy_cell_bl_names = ["dummy_" + x for x in self.cell.get_all_bitline_names()]
|
||||||
# These are the non-indexed names
|
|
||||||
self.dummy_cell_wl_names = ["dummy_"+x for x in self.cell.get_all_wl_names()]
|
|
||||||
self.dummy_cell_bl_names = ["dummy_"+x for x in self.cell.get_all_bitline_names()]
|
|
||||||
self.dummy_row_bl_names = self.bitcell_array_bl_names
|
self.dummy_row_bl_names = self.bitcell_array_bl_names
|
||||||
|
|
||||||
# A dictionary because some ports may have nothing
|
# A dictionary because some ports may have nothing
|
||||||
self.rbl_bl_names = {}
|
self.rbl_bl_names = {}
|
||||||
self.rbl_br_names = {}
|
self.rbl_br_names = {}
|
||||||
self.rbl_wl_names = {}
|
self.rbl_wl_names = {}
|
||||||
|
|
||||||
# Create the full WL names include dummy, replica, and regular bit cells
|
# Create the full WL names include dummy, replica, and regular bit cells
|
||||||
self.replica_col_wl_names = []
|
self.replica_col_wl_names = []
|
||||||
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
|
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
|
||||||
# Left port WLs (one dummy for each port when we allow >1 port)
|
# Left port WLs (one dummy for each port when we allow >1 port)
|
||||||
for port in range(self.left_rbl):
|
for port in range(self.left_rbl):
|
||||||
# Make names for all RBLs
|
# Make names for all RBLs
|
||||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))]
|
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
|
||||||
# Keep track of the pin that is the RBL
|
# Keep track of the pin that is the RBL
|
||||||
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
||||||
self.replica_col_wl_names.extend(wl_names)
|
self.replica_col_wl_names.extend(wl_names)
|
||||||
# Regular WLs
|
# Regular WLs
|
||||||
self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
|
self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
|
||||||
# Right port WLs (one dummy for each port when we allow >1 port)
|
# Right port WLs (one dummy for each port when we allow >1 port)
|
||||||
for port in range(self.left_rbl,self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl, self.left_rbl + self.right_rbl):
|
||||||
# Make names for all RBLs
|
# Make names for all RBLs
|
||||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))]
|
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
|
||||||
# Keep track of the pin that is the RBL
|
# Keep track of the pin that is the RBL
|
||||||
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
||||||
self.replica_col_wl_names.extend(wl_names)
|
self.replica_col_wl_names.extend(wl_names)
|
||||||
|
|
@ -159,14 +195,13 @@ class replica_bitcell_array(design.design):
|
||||||
# Left/right dummy columns are connected identically to the replica column
|
# Left/right dummy columns are connected identically to the replica column
|
||||||
self.dummy_col_wl_names = self.replica_col_wl_names
|
self.dummy_col_wl_names = self.replica_col_wl_names
|
||||||
|
|
||||||
|
|
||||||
# Per port bitline names
|
# Per port bitline names
|
||||||
self.replica_bl_names = {}
|
self.replica_bl_names = {}
|
||||||
self.replica_wl_names = {}
|
self.replica_wl_names = {}
|
||||||
# Array of all port bitline names
|
# Array of all port bitline names
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))]
|
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))]
|
||||||
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))]
|
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))]
|
||||||
# Keep track of the left pins that are the RBL
|
# Keep track of the left pins that are the RBL
|
||||||
self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]]
|
self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]]
|
||||||
self.rbl_br_names[port]=right_names[self.bitcell_ports[port]]
|
self.rbl_br_names[port]=right_names[self.bitcell_ports[port]]
|
||||||
|
|
@ -174,36 +209,33 @@ class replica_bitcell_array(design.design):
|
||||||
bl_names = [x for t in zip(left_names, right_names) for x in t]
|
bl_names = [x for t in zip(left_names, right_names) for x in t]
|
||||||
self.replica_bl_names[port] = bl_names
|
self.replica_bl_names[port] = bl_names
|
||||||
|
|
||||||
wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()]
|
wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()]
|
||||||
#wl_names[port] = "rbl_wl{}".format(port)
|
|
||||||
self.replica_wl_names[port] = wl_names
|
self.replica_wl_names[port] = wl_names
|
||||||
|
|
||||||
|
|
||||||
# External pins
|
# External pins
|
||||||
self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
|
self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
|
||||||
# Need to sort by port order since dictionary values may not be in order
|
# Need to sort by port order since dictionary values may not be in order
|
||||||
bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())]
|
bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())]
|
||||||
br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())]
|
br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())]
|
||||||
for (bl_name,br_name) in zip(bl_names,br_names):
|
for (bl_name, br_name) in zip(bl_names, br_names):
|
||||||
self.add_pin(bl_name,"OUTPUT")
|
self.add_pin(bl_name, "OUTPUT")
|
||||||
self.add_pin(br_name,"OUTPUT")
|
self.add_pin(br_name, "OUTPUT")
|
||||||
self.add_pin_list(self.bitcell_array_wl_names, "INPUT")
|
self.add_pin_list(self.bitcell_array_wl_names, "INPUT")
|
||||||
# Need to sort by port order since dictionary values may not be in order
|
# Need to sort by port order since dictionary values may not be in order
|
||||||
wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())]
|
wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())]
|
||||||
for pin_name in wl_names:
|
for pin_name in wl_names:
|
||||||
self.add_pin(pin_name,"INPUT")
|
self.add_pin(pin_name, "INPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
""" Create the module instances used in this design """
|
""" Create the module instances used in this design """
|
||||||
|
|
||||||
supplies = ["vdd", "gnd"]
|
supplies = ["vdd", "gnd"]
|
||||||
|
|
||||||
# Used for names/dimensions only
|
# Used for names/dimensions only
|
||||||
self.cell = factory.create(module_type="bitcell")
|
self.cell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
# Main array
|
# Main array
|
||||||
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
||||||
mod=self.bitcell_array)
|
mod=self.bitcell_array)
|
||||||
|
|
@ -211,161 +243,162 @@ class replica_bitcell_array(design.design):
|
||||||
|
|
||||||
# Replica columns
|
# Replica columns
|
||||||
self.replica_col_inst = {}
|
self.replica_col_inst = {}
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port),
|
self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port),
|
||||||
mod=self.replica_columns[port])
|
mod=self.replica_columns[port])
|
||||||
self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies)
|
self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies)
|
||||||
|
|
||||||
|
|
||||||
# Dummy rows under the bitcell array (connected with with the replica cell wl)
|
# Dummy rows under the bitcell array (connected with with the replica cell wl)
|
||||||
self.dummy_row_replica_inst = {}
|
self.dummy_row_replica_inst = {}
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port),
|
self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port),
|
||||||
mod=self.dummy_row)
|
mod=self.dummy_row)
|
||||||
self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies)
|
self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies)
|
||||||
|
|
||||||
|
|
||||||
# Top/bottom dummy rows
|
|
||||||
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
|
|
||||||
mod=self.dummy_row)
|
|
||||||
self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies)
|
|
||||||
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
|
|
||||||
mod=self.dummy_row)
|
|
||||||
self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies)
|
|
||||||
|
|
||||||
|
# Top/bottom dummy rows or col caps
|
||||||
|
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
|
||||||
|
mod=self.edge_row)
|
||||||
|
self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies)
|
||||||
|
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
|
||||||
|
mod=self.edge_row)
|
||||||
|
self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies)
|
||||||
|
|
||||||
# Left/right Dummy columns
|
# Left/right Dummy columns
|
||||||
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
|
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
|
||||||
mod=self.dummy_col)
|
mod=self.edge_col_left)
|
||||||
self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
self.connect_inst([x + "_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||||
self.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
|
self.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
|
||||||
mod=self.dummy_col)
|
mod=self.edge_col_right)
|
||||||
self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
|
|
||||||
self.height = (self.row_size+self.extra_rows)*self.dummy_row.height
|
self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
|
||||||
self.width = (self.column_size+self.extra_cols)*self.cell.width
|
self.width = (self.column_size + self.extra_cols) * self.cell.width
|
||||||
|
|
||||||
# This is a bitcell x bitcell offset to scale
|
# This is a bitcell x bitcell offset to scale
|
||||||
offset = vector(self.cell.width, self.cell.height)
|
offset = vector(self.cell.width, self.cell.height)
|
||||||
|
|
||||||
self.bitcell_array_inst.place(offset=[0,0])
|
self.bitcell_array_inst.place(offset=[0, 0])
|
||||||
|
|
||||||
# To the left of the bitcell array
|
# To the left of the bitcell array
|
||||||
for bit in range(self.left_rbl):
|
for bit in range(self.left_rbl):
|
||||||
self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1))
|
self.replica_col_inst[bit].place(offset=offset.scale(-bit - 1, -self.left_rbl - 1))
|
||||||
# To the right of the bitcell array
|
# To the right of the bitcell array
|
||||||
for bit in range(self.right_rbl):
|
for bit in range(self.right_rbl):
|
||||||
self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr())
|
self.replica_col_inst[self.left_rbl + bit].place(offset=offset.scale(bit, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: These depend on the array size itself
|
||||||
# Far top dummy row (first row above array is NOT flipped)
|
# Far top dummy row (first row above array is NOT flipped)
|
||||||
flip_dummy = self.right_rbl%2
|
flip_dummy = self.right_rbl % 2
|
||||||
self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(),
|
self.dummy_row_top_inst.place(offset=offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul(),
|
||||||
mirror="MX" if flip_dummy else "R0")
|
mirror="MX" if flip_dummy else "R0")
|
||||||
|
# FIXME: These depend on the array size itself
|
||||||
# Far bottom dummy row (first row below array IS flipped)
|
# Far bottom dummy row (first row below array IS flipped)
|
||||||
flip_dummy = (self.left_rbl+1)%2
|
flip_dummy = (self.left_rbl + 1) % 2
|
||||||
self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy),
|
self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy),
|
||||||
mirror="MX" if flip_dummy else "R0")
|
mirror="MX" if flip_dummy else "R0")
|
||||||
# Far left dummy col
|
# Far left dummy col
|
||||||
self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1))
|
self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl - 1, -self.left_rbl - 1))
|
||||||
# Far right dummy col
|
# Far right dummy col
|
||||||
self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr())
|
self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
|
||||||
|
|
||||||
# Replica dummy rows
|
# Replica dummy rows
|
||||||
for bit in range(self.left_rbl):
|
for bit in range(self.left_rbl):
|
||||||
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2),
|
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2),
|
||||||
mirror="R0" if bit%2 else "MX")
|
mirror="R0" if bit % 2 else "MX")
|
||||||
for bit in range(self.right_rbl):
|
for bit in range(self.right_rbl):
|
||||||
self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(),
|
self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(),
|
||||||
mirror="MX" if bit%2 else "R0")
|
mirror="MX" if bit % 2 else "R0")
|
||||||
|
|
||||||
|
self.translate_all(offset.scale(-1 - self.left_rbl, -1 - self.left_rbl))
|
||||||
|
|
||||||
self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl))
|
|
||||||
|
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
|
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
""" Add the layout pins """
|
""" Add the layout pins """
|
||||||
|
|
||||||
# Main array wl and bl/br
|
# Main array wl and bl/br
|
||||||
pin_names = self.bitcell_array.get_pin_names()
|
pin_names = self.bitcell_array.get_pin_names()
|
||||||
for pin_name in pin_names:
|
for pin_name in pin_names:
|
||||||
if pin_name.startswith("wl"):
|
for wl in self.bitcell_array_wl_names:
|
||||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
if wl in pin_name:
|
||||||
for pin in pin_list:
|
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||||
self.add_layout_pin(text=pin_name,
|
for pin in pin_list:
|
||||||
layer=pin.layer,
|
self.add_layout_pin(text=pin_name,
|
||||||
offset=pin.ll().scale(0,1),
|
layer=pin.layer,
|
||||||
width=self.width,
|
offset=pin.ll().scale(0, 1),
|
||||||
height=pin.height())
|
width=self.width,
|
||||||
elif pin_name.startswith("bl") or pin_name.startswith("br"):
|
height=pin.height())
|
||||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
for bitline in self.bitcell_array_bl_names:
|
||||||
for pin in pin_list:
|
if bitline in pin_name:
|
||||||
self.add_layout_pin(text=pin_name,
|
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||||
layer=pin.layer,
|
for pin in pin_list:
|
||||||
offset=pin.ll().scale(1,0),
|
self.add_layout_pin(text=pin_name,
|
||||||
width=pin.width(),
|
layer=pin.layer,
|
||||||
height=self.height)
|
offset=pin.ll().scale(1, 0),
|
||||||
|
width=pin.width(),
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
# Replica wordlines
|
# Replica wordlines
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
inst = self.replica_col_inst[port]
|
inst = self.replica_col_inst[port]
|
||||||
for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]):
|
for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]):
|
||||||
# +1 for dummy row
|
# +1 for dummy row
|
||||||
pin_bit = port+1
|
pin_bit = port + 1
|
||||||
# +row_size if above the array
|
# +row_size if above the array
|
||||||
if port>=self.left_rbl:
|
if port>=self.left_rbl:
|
||||||
pin_bit += self.row_size
|
pin_bit += self.row_size
|
||||||
|
|
||||||
pin_name += "_{}".format(pin_bit)
|
pin_name += "_{}".format(pin_bit)
|
||||||
pin = inst.get_pin(pin_name)
|
pin = inst.get_pin(pin_name)
|
||||||
if wl_name in self.rbl_wl_names.values():
|
if wl_name in self.rbl_wl_names.values():
|
||||||
self.add_layout_pin(text=wl_name,
|
self.add_layout_pin(text=wl_name,
|
||||||
layer=pin.layer,
|
layer=pin.layer,
|
||||||
offset=pin.ll().scale(0,1),
|
offset=pin.ll().scale(0, 1),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=pin.height())
|
height=pin.height())
|
||||||
|
|
||||||
|
|
||||||
# Replica bitlines
|
# Replica bitlines
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
inst = self.replica_col_inst[port]
|
inst = self.replica_col_inst[port]
|
||||||
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]):
|
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]):
|
||||||
pin = inst.get_pin(pin_name)
|
pin = inst.get_pin(pin_name)
|
||||||
if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names:
|
if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names:
|
||||||
name = bl_name
|
name = bl_name
|
||||||
else:
|
else:
|
||||||
name = "rbl_{0}_{1}".format(pin_name,port)
|
name = "rbl_{0}_{1}".format(pin_name, port)
|
||||||
self.add_layout_pin(text=name,
|
self.add_layout_pin(text=name,
|
||||||
layer=pin.layer,
|
layer=pin.layer,
|
||||||
offset=pin.ll().scale(1,0),
|
offset=pin.ll().scale(1, 0),
|
||||||
width=pin.width(),
|
width=pin.width(),
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
|
# vdd/gnd are only connected in the perimeter cells
|
||||||
for pin_name in ["vdd","gnd"]:
|
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
|
||||||
for inst in self.insts:
|
supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
|
||||||
|
self.dummy_row_top_inst, self.dummy_row_bot_inst]
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
for inst in supply_insts:
|
||||||
pin_list = inst.get_pins(pin_name)
|
pin_list = inst.get_pins(pin_name)
|
||||||
for pin in pin_list:
|
for pin in pin_list:
|
||||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
self.add_power_pin(name=pin_name,
|
||||||
|
loc=pin.center(),
|
||||||
|
directions=("V", "V"),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
for inst in list(self.replica_col_inst.values()):
|
||||||
|
self.copy_layout_pin(inst, pin_name)
|
||||||
|
self.copy_layout_pin(inst, pin_name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_rbl_wl_name(self, port):
|
def get_rbl_wl_name(self, port):
|
||||||
""" Return the WL for the given RBL port """
|
""" Return the WL for the given RBL port """
|
||||||
return self.rbl_wl_names[port]
|
return self.rbl_wl_names[port]
|
||||||
|
|
||||||
def get_rbl_bl_name(self, port):
|
def get_rbl_bl_name(self, port):
|
||||||
""" Return the BL for the given RBL port """
|
""" Return the BL for the given RBL port """
|
||||||
return self.rbl_bl_names[port]
|
return self.rbl_bl_names[port]
|
||||||
|
|
@ -376,19 +409,17 @@ class replica_bitcell_array(design.design):
|
||||||
|
|
||||||
def analytical_power(self, corner, load):
|
def analytical_power(self, corner, load):
|
||||||
"""Power of Bitcell array and bitline in nW."""
|
"""Power of Bitcell array and bitline in nW."""
|
||||||
from tech import drc, parameter
|
|
||||||
|
|
||||||
# Dynamic Power from Bitline
|
# Dynamic Power from Bitline
|
||||||
bl_wire = self.gen_bl_wire()
|
bl_wire = self.gen_bl_wire()
|
||||||
cell_load = 2 * bl_wire.return_input_cap()
|
cell_load = 2 * bl_wire.return_input_cap()
|
||||||
bl_swing = OPTS.rbl_delay_percentage
|
bl_swing = OPTS.rbl_delay_percentage
|
||||||
freq = spice["default_event_frequency"]
|
freq = spice["default_event_frequency"]
|
||||||
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
||||||
|
|
||||||
#Calculate the bitcell power which currently only includes leakage
|
# Calculate the bitcell power which currently only includes leakage
|
||||||
cell_power = self.cell.analytical_power(corner, load)
|
cell_power = self.cell.analytical_power(corner, load)
|
||||||
|
|
||||||
#Leakage power grows with entire array and bitlines.
|
# Leakage power grows with entire array and bitlines.
|
||||||
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
||||||
cell_power.leakage * self.column_size * self.row_size)
|
cell_power.leakage * self.column_size * self.row_size)
|
||||||
return total_power
|
return total_power
|
||||||
|
|
@ -399,13 +430,13 @@ class replica_bitcell_array(design.design):
|
||||||
else:
|
else:
|
||||||
height = self.height
|
height = self.height
|
||||||
bl_pos = 0
|
bl_pos = 0
|
||||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
|
||||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||||
return bl_wire
|
return bl_wire
|
||||||
|
|
||||||
def get_wordline_cin(self):
|
def get_wordline_cin(self):
|
||||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||||
total_cin = bitcell_wl_cin * self.column_size
|
total_cin = bitcell_wl_cin * self.column_size
|
||||||
return total_cin
|
return total_cin
|
||||||
|
|
@ -413,13 +444,13 @@ class replica_bitcell_array(design.design):
|
||||||
def graph_exclude_bits(self, targ_row, targ_col):
|
def graph_exclude_bits(self, targ_row, targ_col):
|
||||||
"""Excludes bits in column from being added to graph except target"""
|
"""Excludes bits in column from being added to graph except target"""
|
||||||
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
|
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
|
||||||
|
|
||||||
def graph_exclude_replica_col_bits(self):
|
def graph_exclude_replica_col_bits(self):
|
||||||
"""Exclude all replica/dummy cells in the replica columns except the replica bit."""
|
"""Exclude all replica/dummy cells in the replica columns except the replica bit."""
|
||||||
|
|
||||||
for port in range(self.left_rbl+self.right_rbl):
|
for port in range(self.left_rbl + self.right_rbl):
|
||||||
self.replica_columns[port].exclude_all_but_replica()
|
self.replica_columns[port].exclude_all_but_replica()
|
||||||
|
|
||||||
def get_cell_name(self, inst_name, row, col):
|
def get_cell_name(self, inst_name, row, col):
|
||||||
"""Gets the spice name of the target bitcell."""
|
"""Gets the spice name of the target bitcell."""
|
||||||
return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col)
|
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col)
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
# See LICENSE for licensing information.
|
# See LICENSE for licensing information.
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
import debug
|
import debug
|
||||||
import design
|
import design
|
||||||
from tech import drc
|
from tech import cell_properties
|
||||||
import contact
|
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
|
|
||||||
|
|
||||||
class replica_column(design.design):
|
class replica_column(design.design):
|
||||||
"""
|
"""
|
||||||
Generate a replica bitline column for the replica array.
|
Generate a replica bitline column for the replica array.
|
||||||
Rows is the total number of rows i the main array.
|
Rows is the total number of rows i the main array.
|
||||||
Left_rbl and right_rbl are the number of left and right replica bitlines.
|
Left_rbl and right_rbl are the number of left and right replica bitlines.
|
||||||
Replica bit specifies which replica column this is (to determine where to put the
|
Replica bit specifies which replica column this is (to determine where to put the
|
||||||
replica cell.
|
replica cell.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, rows, left_rbl, right_rbl, replica_bit):
|
def __init__(self, name, rows, left_rbl, right_rbl, replica_bit,
|
||||||
|
column_offset=0):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
|
|
@ -28,24 +29,30 @@ class replica_column(design.design):
|
||||||
self.right_rbl = right_rbl
|
self.right_rbl = right_rbl
|
||||||
self.replica_bit = replica_bit
|
self.replica_bit = replica_bit
|
||||||
# left, right, regular rows plus top/bottom dummy cells
|
# left, right, regular rows plus top/bottom dummy cells
|
||||||
self.total_size = self.left_rbl+rows+self.right_rbl+2
|
self.total_size = self.left_rbl + rows + self.right_rbl + 2
|
||||||
|
self.column_offset = column_offset
|
||||||
debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.")
|
|
||||||
debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1,
|
debug.check(replica_bit != 0 and replica_bit != rows,
|
||||||
"Replica bit cannot be in the regular array.")
|
"Replica bit cannot be the dummy row.")
|
||||||
|
debug.check(replica_bit <= left_rbl or replica_bit >= self.total_size - right_rbl - 1,
|
||||||
|
"Replica bit cannot be in the regular array.")
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
debug.check(rows % 2 == 0 and (left_rbl + 1) % 2 == 0,
|
||||||
|
"sky130 currently requires rows to be even and to start with X mirroring"
|
||||||
|
+ " (left_rbl must be odd) for LVS.")
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_instances()
|
self.create_instances()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.height = self.total_size*self.cell.height
|
self.height = self.total_size * self.cell.height
|
||||||
self.width = self.cell.width
|
self.width = self.cell.width
|
||||||
|
|
||||||
self.place_instances()
|
self.place_instances()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
@ -53,109 +60,170 @@ class replica_column(design.design):
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
|
|
||||||
for bl_name in self.cell.get_all_bitline_names():
|
for bl_name in self.cell.get_all_bitline_names():
|
||||||
# In the replica column, these are only outputs!
|
# In the replica column, these are only outputs!
|
||||||
self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT")
|
self.add_pin("{0}_{1}".format(bl_name, 0), "OUTPUT")
|
||||||
|
|
||||||
for row in range(self.total_size):
|
for row in range(self.total_size):
|
||||||
for wl_name in self.cell.get_all_wl_names():
|
for wl_name in self.cell.get_all_wl_names():
|
||||||
self.add_pin("{0}_{1}".format(wl_name,row), "INPUT")
|
self.add_pin("{0}_{1}".format(wl_name, row), "INPUT")
|
||||||
|
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.replica_cell = factory.create(module_type="replica_bitcell")
|
self.replica_cell = factory.create(module_type="replica_{}".format(OPTS.bitcell))
|
||||||
self.add_mod(self.replica_cell)
|
self.add_mod(self.replica_cell)
|
||||||
self.dummy_cell = factory.create(module_type="dummy_bitcell")
|
self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
|
||||||
self.add_mod(self.dummy_cell)
|
self.add_mod(self.dummy_cell)
|
||||||
|
try:
|
||||||
|
edge_module_type = ("col_cap" if cell_properties.bitcell.end_caps else "dummy")
|
||||||
|
except AttributeError:
|
||||||
|
edge_module_type = "dummy"
|
||||||
|
self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell)
|
||||||
|
self.add_mod(self.edge_cell)
|
||||||
# Used for pin names only
|
# Used for pin names only
|
||||||
self.cell = factory.create(module_type="bitcell")
|
self.cell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
def create_instances(self):
|
def create_instances(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||||
|
except AttributeError:
|
||||||
|
end_caps_enabled = False
|
||||||
|
|
||||||
self.cell_inst = {}
|
self.cell_inst = {}
|
||||||
for row in range(self.total_size):
|
for row in range(self.total_size):
|
||||||
name="rbc_{0}".format(row)
|
name="rbc_{0}".format(row)
|
||||||
# Top/bottom cell are always dummy cells.
|
# Top/bottom cell are always dummy cells.
|
||||||
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
|
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
|
||||||
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
||||||
if (row>self.left_rbl and row<self.total_size-self.right_rbl-1):
|
if (row > self.left_rbl and row < self.total_size - self.right_rbl - 1):
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
self.cell_inst[row]=self.add_inst(name=name,
|
||||||
mod=self.replica_cell)
|
mod=self.replica_cell)
|
||||||
|
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||||
elif row==self.replica_bit:
|
elif row==self.replica_bit:
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
self.cell_inst[row]=self.add_inst(name=name,
|
||||||
mod=self.replica_cell)
|
mod=self.replica_cell)
|
||||||
|
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||||
|
elif (row == 0 or row == self.total_size - 1):
|
||||||
|
self.cell_inst[row]=self.add_inst(name=name,
|
||||||
|
mod=self.edge_cell)
|
||||||
|
if end_caps_enabled:
|
||||||
|
self.connect_inst(self.get_bitcell_pins_col_cap(0, row))
|
||||||
|
else:
|
||||||
|
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||||
else:
|
else:
|
||||||
self.cell_inst[row]=self.add_inst(name=name,
|
self.cell_inst[row]=self.add_inst(name=name,
|
||||||
mod=self.dummy_cell)
|
mod=self.dummy_cell)
|
||||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||||
|
|
||||||
def place_instances(self):
|
|
||||||
|
|
||||||
|
def place_instances(self):
|
||||||
|
from tech import cell_properties
|
||||||
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
|
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
|
||||||
# so that we will start with mirroring rather than not mirroring
|
# so that we will start with mirroring rather than not mirroring
|
||||||
rbl_offset = (self.left_rbl+1)%2
|
rbl_offset = (self.left_rbl + 1) %2
|
||||||
|
|
||||||
|
# if our bitcells are mirrored on the y axis, check if we are in global
|
||||||
|
# column that needs to be flipped.
|
||||||
|
dir_y = False
|
||||||
|
xoffset = 0
|
||||||
|
if cell_properties.bitcell.mirror.y and self.column_offset % 2:
|
||||||
|
dir_y = True
|
||||||
|
xoffset = self.replica_cell.width
|
||||||
|
|
||||||
for row in range(self.total_size):
|
for row in range(self.total_size):
|
||||||
name = "bit_r{0}_{1}".format(row,"rbl")
|
# name = "bit_r{0}_{1}".format(row, "rbl")
|
||||||
offset = vector(0,self.cell.height*(row+(row+rbl_offset)%2))
|
dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2
|
||||||
if (row+rbl_offset)%2:
|
|
||||||
|
offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2))
|
||||||
|
|
||||||
|
if dir_x and dir_y:
|
||||||
|
dir_key = "XY"
|
||||||
|
elif dir_x:
|
||||||
dir_key = "MX"
|
dir_key = "MX"
|
||||||
|
elif dir_y:
|
||||||
|
dir_key = "MY"
|
||||||
else:
|
else:
|
||||||
dir_key = "R0"
|
dir_key = ""
|
||||||
|
|
||||||
self.cell_inst[row].place(offset=offset,
|
self.cell_inst[row].place(offset=offset,
|
||||||
mirror=dir_key)
|
mirror=dir_key)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
""" Add the layout pins """
|
""" Add the layout pins """
|
||||||
|
|
||||||
for bl_name in self.cell.get_all_bitline_names():
|
for bl_name in self.cell.get_all_bitline_names():
|
||||||
bl_pin = self.cell_inst[0].get_pin(bl_name)
|
bl_pin = self.cell_inst[0].get_pin(bl_name)
|
||||||
self.add_layout_pin(text=bl_name,
|
self.add_layout_pin(text=bl_name,
|
||||||
layer="metal2",
|
layer=bl_pin.layer,
|
||||||
offset=bl_pin.ll(),
|
offset=bl_pin.ll().scale(1, 0),
|
||||||
width=bl_pin.width(),
|
width=bl_pin.width(),
|
||||||
height=self.height)
|
height=self.height)
|
||||||
|
|
||||||
for row in range(self.total_size):
|
try:
|
||||||
|
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||||
|
except AttributeError:
|
||||||
|
end_caps_enabled = False
|
||||||
|
|
||||||
|
if end_caps_enabled:
|
||||||
|
row_range_max = self.total_size - 1
|
||||||
|
row_range_min = 1
|
||||||
|
else:
|
||||||
|
row_range_max = self.total_size
|
||||||
|
row_range_min = 0
|
||||||
|
|
||||||
|
for row in range(row_range_min, row_range_max):
|
||||||
for wl_name in self.cell.get_all_wl_names():
|
for wl_name in self.cell.get_all_wl_names():
|
||||||
wl_pin = self.cell_inst[row].get_pin(wl_name)
|
wl_pin = self.cell_inst[row].get_pin(wl_name)
|
||||||
self.add_layout_pin(text="{0}_{1}".format(wl_name,row),
|
self.add_layout_pin(text="{0}_{1}".format(wl_name, row),
|
||||||
layer="metal1",
|
layer=wl_pin.layer,
|
||||||
offset=wl_pin.ll().scale(0,1),
|
offset=wl_pin.ll().scale(0, 1),
|
||||||
width=self.width,
|
width=self.width,
|
||||||
height=wl_pin.height())
|
height=wl_pin.height())
|
||||||
|
|
||||||
# For every second row and column, add a via for gnd and vdd
|
# Supplies are only connected in the ends
|
||||||
for row in range(self.total_size):
|
for (index, inst) in self.cell_inst.items():
|
||||||
inst = self.cell_inst[row]
|
|
||||||
for pin_name in ["vdd", "gnd"]:
|
for pin_name in ["vdd", "gnd"]:
|
||||||
self.copy_layout_pin(inst, pin_name)
|
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
||||||
|
self.copy_power_pins(inst, pin_name)
|
||||||
|
else:
|
||||||
|
self.copy_layout_pin(inst, pin_name)
|
||||||
|
|
||||||
def get_bitcell_pins(self, col, row):
|
def get_bitcell_pins(self, col, row):
|
||||||
""" Creates a list of connections in the bitcell,
|
""" Creates a list of connections in the bitcell,
|
||||||
indexed by column and row, for instance use in bitcell_array """
|
indexed by column and row, for instance use in bitcell_array """
|
||||||
|
|
||||||
bitcell_pins = []
|
bitcell_pins = []
|
||||||
|
|
||||||
pin_names = self.cell.get_all_bitline_names()
|
pin_names = self.cell.get_all_bitline_names()
|
||||||
for pin in pin_names:
|
for pin in pin_names:
|
||||||
bitcell_pins.append(pin+"_{0}".format(col))
|
bitcell_pins.append(pin + "_{0}".format(col))
|
||||||
pin_names = self.cell.get_all_wl_names()
|
pin_names = self.cell.get_all_wl_names()
|
||||||
for pin in pin_names:
|
for pin in pin_names:
|
||||||
bitcell_pins.append(pin+"_{0}".format(row))
|
bitcell_pins.append(pin + "_{0}".format(row))
|
||||||
bitcell_pins.append("vdd")
|
bitcell_pins.append("vdd")
|
||||||
bitcell_pins.append("gnd")
|
bitcell_pins.append("gnd")
|
||||||
|
|
||||||
return bitcell_pins
|
return bitcell_pins
|
||||||
|
|
||||||
|
def get_bitcell_pins_col_cap(self, col, row):
|
||||||
|
""" Creates a list of connections in the bitcell,
|
||||||
|
indexed by column and row, for instance use in bitcell_array """
|
||||||
|
|
||||||
|
bitcell_pins = []
|
||||||
|
|
||||||
|
pin_names = self.cell.get_all_bitline_names()
|
||||||
|
for pin in pin_names:
|
||||||
|
bitcell_pins.append(pin + "_{0}".format(col))
|
||||||
|
bitcell_pins.append("vdd")
|
||||||
|
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
def exclude_all_but_replica(self):
|
def exclude_all_but_replica(self):
|
||||||
"""Excludes all bits except the replica cell (self.replica_bit)."""
|
"""Excludes all bits except the replica cell (self.replica_bit)."""
|
||||||
|
|
||||||
for row, cell in self.cell_inst.items():
|
for row, cell in self.cell_inst.items():
|
||||||
if row != self.replica_bit:
|
if row != self.replica_bit:
|
||||||
self.graph_inst_exclude.add(cell)
|
self.graph_inst_exclude.add(cell)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
from bitcell_base_array import bitcell_base_array
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
from tech import cell_properties
|
||||||
|
|
||||||
|
|
||||||
|
class row_cap_array(bitcell_base_array):
|
||||||
|
"""
|
||||||
|
Generate a dummy row/column for the replica array.
|
||||||
|
"""
|
||||||
|
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
|
||||||
|
super().__init__(cols, rows, name, column_offset)
|
||||||
|
self.mirror = mirror
|
||||||
|
self.no_instances = True
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
""" Create and connect the netlist """
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_instances()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
|
||||||
|
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||||
|
self.add_layout_pins()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
""" Add the modules used in this design """
|
||||||
|
self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell))
|
||||||
|
self.add_mod(self.dummy_cell)
|
||||||
|
|
||||||
|
self.cell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
|
def create_instances(self):
|
||||||
|
""" Create the module instances used in this design """
|
||||||
|
self.cell_inst = {}
|
||||||
|
for col in range(self.column_size):
|
||||||
|
for row in range(1, self.row_size - 1):
|
||||||
|
name = "bit_r{0}_c{1}".format(row, col)
|
||||||
|
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||||
|
mod=self.dummy_cell)
|
||||||
|
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||||
|
|
||||||
|
def get_bitcell_pins(self, col, row):
|
||||||
|
"""
|
||||||
|
Creates a list of connections in the bitcell,
|
||||||
|
indexed by column and row, for instance use in bitcell_array
|
||||||
|
"""
|
||||||
|
|
||||||
|
pin_name = cell_properties.bitcell.cell_1rw1r.pin
|
||||||
|
bitcell_pins = ["{0}_{1}".format(pin_name.wl0, row),
|
||||||
|
"{0}_{1}".format(pin_name.wl1, row),
|
||||||
|
"gnd"]
|
||||||
|
|
||||||
|
return bitcell_pins
|
||||||
|
|
||||||
|
def place_array(self, name_template, row_offset=0):
|
||||||
|
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||||
|
self.height = self.row_size * self.cell.height
|
||||||
|
self.width = self.column_size * self.cell.width
|
||||||
|
|
||||||
|
xoffset = 0.0
|
||||||
|
for col in range(self.column_size):
|
||||||
|
yoffset = self.cell.height
|
||||||
|
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
||||||
|
|
||||||
|
for row in range(1, self.row_size - 1):
|
||||||
|
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
|
||||||
|
|
||||||
|
if dir_x and dir_y:
|
||||||
|
dir_key = "XY"
|
||||||
|
elif dir_x:
|
||||||
|
dir_key = "MX"
|
||||||
|
elif dir_y:
|
||||||
|
dir_key = "MY"
|
||||||
|
else:
|
||||||
|
dir_key = ""
|
||||||
|
|
||||||
|
self.cell_inst[row, col].place(offset=[tempx, tempy],
|
||||||
|
mirror=dir_key)
|
||||||
|
yoffset += self.cell.height
|
||||||
|
xoffset += self.cell.width
|
||||||
|
|
||||||
|
def add_layout_pins(self):
|
||||||
|
""" Add the layout pins """
|
||||||
|
|
||||||
|
row_list = self.cell.get_all_wl_names()
|
||||||
|
|
||||||
|
for row in range(1, self.row_size - 1):
|
||||||
|
for cell_row in row_list:
|
||||||
|
wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
|
||||||
|
self.add_layout_pin(text=cell_row + "_{0}".format(row),
|
||||||
|
layer=wl_pin.layer,
|
||||||
|
offset=wl_pin.ll().scale(0, 1),
|
||||||
|
width=self.width,
|
||||||
|
height=wl_pin.height())
|
||||||
|
|
||||||
|
# Add vdd/gnd via stacks
|
||||||
|
for row in range(1, self.row_size - 1):
|
||||||
|
for col in range(self.column_size):
|
||||||
|
inst = self.cell_inst[row, col]
|
||||||
|
for pin_name in ["vdd", "gnd"]:
|
||||||
|
for pin in inst.get_pins(pin_name):
|
||||||
|
self.add_power_pin(name=pin.name,
|
||||||
|
loc=pin.center(),
|
||||||
|
start_layer=pin.layer)
|
||||||
|
|
||||||
|
|
@ -8,9 +8,12 @@
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
import utils
|
import utils
|
||||||
from tech import GDS,layer, parameter,drc
|
from tech import GDS, layer, parameter, drc
|
||||||
|
from tech import cell_properties as props
|
||||||
|
from globals import OPTS
|
||||||
import logical_effort
|
import logical_effort
|
||||||
|
|
||||||
|
|
||||||
class sense_amp(design.design):
|
class sense_amp(design.design):
|
||||||
"""
|
"""
|
||||||
This module implements the single sense amp cell used in the design. It
|
This module implements the single sense amp cell used in the design. It
|
||||||
|
|
@ -18,11 +21,33 @@ class sense_amp(design.design):
|
||||||
the technology library.
|
the technology library.
|
||||||
Sense amplifier to read a pair of bit-lines.
|
Sense amplifier to read a pair of bit-lines.
|
||||||
"""
|
"""
|
||||||
|
pin_names = [props.sense_amp.pin.bl,
|
||||||
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
props.sense_amp.pin.br,
|
||||||
|
props.sense_amp.pin.dout,
|
||||||
|
props.sense_amp.pin.en,
|
||||||
|
props.sense_amp.pin.vdd,
|
||||||
|
props.sense_amp.pin.gnd]
|
||||||
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
if not OPTS.netlist_only:
|
||||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
(width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||||
|
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||||
|
else:
|
||||||
|
(width, height) = (0, 0)
|
||||||
|
pin_map = []
|
||||||
|
|
||||||
|
def get_bl_names(self):
|
||||||
|
return props.sense_amp.pin.bl
|
||||||
|
|
||||||
|
def get_br_names(self):
|
||||||
|
return props.sense_amp.pin.br
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dout_name(self):
|
||||||
|
return props.sense_amp.pin.dout
|
||||||
|
|
||||||
|
@property
|
||||||
|
def en_name(self):
|
||||||
|
return props.sense_amp.pin.en
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
|
|
@ -37,42 +62,41 @@ class sense_amp(design.design):
|
||||||
|
|
||||||
# FIXME: This input load will be applied to both the s_en timing and bitline timing.
|
# FIXME: This input load will be applied to both the s_en timing and bitline timing.
|
||||||
|
|
||||||
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
# Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||||
from tech import spice, parameter
|
from tech import spice
|
||||||
# Default is 8x. Per Samira and Hodges-Jackson book:
|
# Default is 8x. Per Samira and Hodges-Jackson book:
|
||||||
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
|
# "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.
|
bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||||
return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff
|
return spice["min_tx_drain_c"] * bitline_pmos_size # ff
|
||||||
|
|
||||||
def get_stage_effort(self, load):
|
def get_stage_effort(self, load):
|
||||||
#Delay of the sense amp will depend on the size of the amp and the output load.
|
# Delay of the sense amp will depend on the size of the amp and the output load.
|
||||||
parasitic_delay = 1
|
parasitic_delay = 1
|
||||||
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")
|
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx")
|
||||||
sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx")
|
sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx")
|
||||||
cc_inv_cin = cin
|
cc_inv_cin = cin
|
||||||
return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False)
|
return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False)
|
||||||
|
|
||||||
def analytical_power(self, corner, load):
|
def analytical_power(self, corner, load):
|
||||||
"""Returns dynamic and leakage power. Results in nW"""
|
"""Returns dynamic and leakage power. Results in nW"""
|
||||||
#Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
# Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
||||||
total_power = self.return_power()
|
total_power = self.return_power()
|
||||||
return total_power
|
return total_power
|
||||||
|
|
||||||
def get_en_cin(self):
|
def get_en_cin(self):
|
||||||
"""Get the relative capacitance of sense amp enable gate cin"""
|
"""Get the relative capacitance of sense amp enable gate cin"""
|
||||||
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx")
|
pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx")
|
||||||
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
|
nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx")
|
||||||
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
# sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||||
return 2*pmos_cin + nmos_cin
|
return 2 * pmos_cin + nmos_cin
|
||||||
|
|
||||||
def get_enable_name(self):
|
def get_enable_name(self):
|
||||||
"""Returns name used for enable net"""
|
"""Returns name used for enable net"""
|
||||||
#FIXME: A better programmatic solution to designate pins
|
# FIXME: A better programmatic solution to designate pins
|
||||||
enable_name = "en"
|
enable_name = self.en_name
|
||||||
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
|
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
|
||||||
return enable_name
|
return enable_name
|
||||||
|
|
||||||
def build_graph(self, graph, inst_name, port_nets):
|
def build_graph(self, graph, inst_name, port_nets):
|
||||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||||
self.add_graph_edges(graph, port_nets)
|
self.add_graph_edges(graph, port_nets)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,54 @@ import debug
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
import logical_effort
|
import logical_effort
|
||||||
|
|
||||||
|
|
||||||
class sense_amp_array(design.design):
|
class sense_amp_array(design.design):
|
||||||
"""
|
"""
|
||||||
Array of sense amplifiers to read the bitlines through the column mux.
|
Array of sense amplifiers to read the bitlines through the column mux.
|
||||||
Dynamically generated sense amp array for all bitlines.
|
Dynamically generated sense amp array for all bitlines.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, word_size, words_per_row):
|
def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0):
|
||||||
|
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
self.add_comment("word_size {0}".format(word_size))
|
self.add_comment("word_size {0}".format(word_size))
|
||||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||||
|
|
||||||
self.word_size = word_size
|
self.word_size = word_size
|
||||||
self.words_per_row = words_per_row
|
self.words_per_row = words_per_row
|
||||||
|
if not num_spare_cols:
|
||||||
|
self.num_spare_cols = 0
|
||||||
|
else:
|
||||||
|
self.num_spare_cols = num_spare_cols
|
||||||
|
|
||||||
|
self.column_offset = column_offset
|
||||||
self.row_size = self.word_size * self.words_per_row
|
self.row_size = self.word_size * self.words_per_row
|
||||||
|
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
self.en_layer = "m3"
|
||||||
|
else:
|
||||||
|
self.en_layer = "m1"
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
def get_bl_name(self):
|
||||||
|
bl_name = "bl"
|
||||||
|
return bl_name
|
||||||
|
|
||||||
|
def get_br_name(self):
|
||||||
|
br_name = "br"
|
||||||
|
return br_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data_name(self):
|
||||||
|
return "data"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def en_name(self):
|
||||||
|
return "en"
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
|
|
@ -41,11 +69,11 @@ class sense_amp_array(design.design):
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.height = self.amp.height
|
self.height = self.amp.height
|
||||||
|
|
||||||
if self.bitcell.width > self.amp.width:
|
if self.bitcell.width > self.amp.width:
|
||||||
self.width = self.bitcell.width * self.word_size * self.words_per_row
|
self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols)
|
||||||
else:
|
else:
|
||||||
self.width = self.amp.width * self.word_size * self.words_per_row
|
self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols)
|
||||||
|
|
||||||
self.place_sense_amp_array()
|
self.place_sense_amp_array()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
@ -54,105 +82,126 @@ class sense_amp_array(design.design):
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for i in range(0,self.word_size):
|
for i in range(0, self.word_size + self.num_spare_cols):
|
||||||
self.add_pin("data_{0}".format(i), "OUTPUT")
|
self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT")
|
||||||
self.add_pin("bl_{0}".format(i), "INPUT")
|
self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT")
|
||||||
self.add_pin("br_{0}".format(i), "INPUT")
|
self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT")
|
||||||
self.add_pin("en", "INPUT")
|
self.add_pin(self.en_name, "INPUT")
|
||||||
self.add_pin("vdd", "POWER")
|
self.add_pin("vdd", "POWER")
|
||||||
self.add_pin("gnd", "GROUND")
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.amp = factory.create(module_type="sense_amp")
|
self.amp = factory.create(module_type="sense_amp")
|
||||||
|
|
||||||
self.add_mod(self.amp)
|
self.add_mod(self.amp)
|
||||||
|
|
||||||
# This is just used for measurements,
|
# This is just used for measurements,
|
||||||
# so don't add the module
|
# so don't add the module
|
||||||
self.bitcell = factory.create(module_type="bitcell")
|
self.bitcell = factory.create(module_type="bitcell")
|
||||||
|
|
||||||
def create_sense_amp_array(self):
|
def create_sense_amp_array(self):
|
||||||
self.local_insts = []
|
self.local_insts = []
|
||||||
for i in range(0,self.word_size):
|
for i in range(0, self.word_size + self.num_spare_cols):
|
||||||
|
|
||||||
name = "sa_d{0}".format(i)
|
name = "sa_d{0}".format(i)
|
||||||
self.local_insts.append(self.add_inst(name=name,
|
self.local_insts.append(self.add_inst(name=name,
|
||||||
mod=self.amp))
|
mod=self.amp))
|
||||||
self.connect_inst(["bl_{0}".format(i),
|
self.connect_inst([self.get_bl_name() + "_{0}".format(i),
|
||||||
"br_{0}".format(i),
|
self.get_br_name() + "_{0}".format(i),
|
||||||
"data_{0}".format(i),
|
self.data_name + "_{0}".format(i),
|
||||||
"en", "vdd", "gnd"])
|
self.en_name, "vdd", "gnd"])
|
||||||
|
|
||||||
def place_sense_amp_array(self):
|
def place_sense_amp_array(self):
|
||||||
|
from tech import cell_properties
|
||||||
if self.bitcell.width > self.amp.width:
|
|
||||||
amp_spacing = self.bitcell.width * self.words_per_row
|
for i in range(0, self.row_size, self.words_per_row):
|
||||||
else:
|
index = int(i / self.words_per_row)
|
||||||
amp_spacing = self.amp.width * self.words_per_row
|
xoffset = i * self.bitcell.width
|
||||||
for i in range(0,self.word_size):
|
|
||||||
amp_position = vector(amp_spacing * i, 0)
|
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||||
self.local_insts[i].place(amp_position)
|
mirror = "MY"
|
||||||
|
xoffset = xoffset + self.amp.width
|
||||||
|
else:
|
||||||
|
mirror = ""
|
||||||
|
|
||||||
|
amp_position = vector(xoffset, 0)
|
||||||
|
self.local_insts[index].place(offset=amp_position, mirror=mirror)
|
||||||
|
|
||||||
|
# place spare sense amps (will share the same enable as regular sense amps)
|
||||||
|
for i in range(0, self.num_spare_cols):
|
||||||
|
index = self.word_size + i
|
||||||
|
xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width
|
||||||
|
|
||||||
|
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||||
|
mirror = "MY"
|
||||||
|
xoffset = xoffset + self.amp.width
|
||||||
|
else:
|
||||||
|
mirror = ""
|
||||||
|
|
||||||
|
amp_position = vector(xoffset, 0)
|
||||||
|
self.local_insts[index].place(offset=amp_position, mirror=mirror)
|
||||||
|
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
for i in range(len(self.local_insts)):
|
for i in range(len(self.local_insts)):
|
||||||
inst = self.local_insts[i]
|
inst = self.local_insts[i]
|
||||||
|
|
||||||
gnd_pos = inst.get_pin("gnd").center()
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=gnd_pos)
|
|
||||||
self.add_layout_pin_rect_center(text="gnd",
|
|
||||||
layer="metal3",
|
|
||||||
offset=gnd_pos)
|
|
||||||
vdd_pos = inst.get_pin("vdd").center()
|
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
|
||||||
offset=vdd_pos)
|
|
||||||
self.add_layout_pin_rect_center(text="vdd",
|
|
||||||
layer="metal3",
|
|
||||||
offset=vdd_pos)
|
|
||||||
|
|
||||||
bl_pin = inst.get_pin("bl")
|
for gnd_pin in inst.get_pins("gnd"):
|
||||||
br_pin = inst.get_pin("br")
|
self.add_power_pin(name="gnd",
|
||||||
dout_pin = inst.get_pin("dout")
|
loc=gnd_pin.center(),
|
||||||
|
start_layer=gnd_pin.layer,
|
||||||
self.add_layout_pin(text="bl_{0}".format(i),
|
directions=("V", "V"))
|
||||||
layer="metal2",
|
|
||||||
|
for vdd_pin in inst.get_pins("vdd"):
|
||||||
|
self.add_power_pin(name="vdd",
|
||||||
|
loc=vdd_pin.center(),
|
||||||
|
start_layer=vdd_pin.layer,
|
||||||
|
directions=("V", "V"))
|
||||||
|
|
||||||
|
bl_pin = inst.get_pin(inst.mod.get_bl_names())
|
||||||
|
br_pin = inst.get_pin(inst.mod.get_br_names())
|
||||||
|
dout_pin = inst.get_pin(inst.mod.dout_name)
|
||||||
|
|
||||||
|
self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i),
|
||||||
|
layer=bl_pin.layer,
|
||||||
offset=bl_pin.ll(),
|
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=self.get_br_name() + "_{0}".format(i),
|
||||||
layer="metal2",
|
layer=br_pin.layer,
|
||||||
offset=br_pin.ll(),
|
offset=br_pin.ll(),
|
||||||
width=br_pin.width(),
|
width=br_pin.width(),
|
||||||
height=br_pin.height())
|
height=br_pin.height())
|
||||||
|
|
||||||
self.add_layout_pin(text="data_{0}".format(i),
|
self.add_layout_pin(text=self.data_name + "_{0}".format(i),
|
||||||
layer="metal2",
|
layer=dout_pin.layer,
|
||||||
offset=dout_pin.ll(),
|
offset=dout_pin.ll(),
|
||||||
width=dout_pin.width(),
|
width=dout_pin.width(),
|
||||||
height=dout_pin.height())
|
height=dout_pin.height())
|
||||||
|
|
||||||
|
|
||||||
def route_rails(self):
|
def route_rails(self):
|
||||||
# add sclk rail across entire array
|
# Add enable across the array
|
||||||
sclk_offset = self.amp.get_pin("en").ll().scale(0,1)
|
en_pin = self.amp.get_pin(self.amp.en_name)
|
||||||
self.add_layout_pin(text="en",
|
start_offset = en_pin.lc().scale(0, 1)
|
||||||
layer="metal1",
|
end_offset = start_offset + vector(self.width, 0)
|
||||||
offset=sclk_offset,
|
self.add_layout_pin_segment_center(text=self.en_name,
|
||||||
width=self.width,
|
layer=self.en_layer,
|
||||||
height=drc("minwidth_metal1"))
|
start=start_offset,
|
||||||
|
end=end_offset)
|
||||||
|
for inst in self.local_insts:
|
||||||
|
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||||
|
to_layer=self.en_layer,
|
||||||
|
offset=inst.get_pin(self.amp.en_name).center())
|
||||||
|
|
||||||
def input_load(self):
|
def input_load(self):
|
||||||
return self.amp.input_load()
|
return self.amp.input_load()
|
||||||
|
|
||||||
def get_en_cin(self):
|
def get_en_cin(self):
|
||||||
"""Get the relative capacitance of all the sense amp enable connections in the array"""
|
"""Get the relative capacitance of all the sense amp enable connections in the array"""
|
||||||
sense_amp_en_cin = self.amp.get_en_cin()
|
sense_amp_en_cin = self.amp.get_en_cin()
|
||||||
return sense_amp_en_cin * self.word_size
|
return sense_amp_en_cin * self.word_size
|
||||||
|
|
||||||
def get_drain_cin(self):
|
def get_drain_cin(self):
|
||||||
"""Get the relative capacitance of the drain of the PMOS isolation TX"""
|
"""Get the relative capacitance of the drain of the PMOS isolation TX"""
|
||||||
from tech import parameter
|
from tech import parameter
|
||||||
#Bitcell drain load being used to estimate PMOS drain load
|
# Bitcell drain load being used to estimate PMOS drain load
|
||||||
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
||||||
return drain_load
|
return drain_load
|
||||||
|
|
|
||||||
|
|
@ -5,56 +5,77 @@
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
from math import log
|
|
||||||
import design
|
import design
|
||||||
import contact
|
|
||||||
from tech import drc
|
|
||||||
import debug
|
import debug
|
||||||
import math
|
from tech import layer, preferred_directions
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from sram_factory import factory
|
from sram_factory import factory
|
||||||
from globals import OPTS
|
from globals import OPTS
|
||||||
import logical_effort
|
import logical_effort
|
||||||
|
|
||||||
|
|
||||||
class single_level_column_mux_array(design.design):
|
class single_level_column_mux_array(design.design):
|
||||||
"""
|
"""
|
||||||
Dynamically generated column mux array.
|
Dynamically generated column mux array.
|
||||||
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, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"):
|
def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0):
|
||||||
design.design.__init__(self, name)
|
design.design.__init__(self, name)
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br))
|
self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br))
|
||||||
|
|
||||||
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.bitcell_bl = bitcell_bl
|
self.bitcell_bl = bitcell_bl
|
||||||
self.bitcell_br = bitcell_br
|
self.bitcell_br = bitcell_br
|
||||||
|
self.column_offset = column_offset
|
||||||
|
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
self.sel_layer = "m3"
|
||||||
|
self.sel_pitch = self.m3_pitch
|
||||||
|
self.bitline_layer = "m1"
|
||||||
|
else:
|
||||||
|
self.sel_layer = "m1"
|
||||||
|
self.sel_pitch = self.m2_pitch
|
||||||
|
self.bitline_layer = "m2"
|
||||||
|
|
||||||
|
if preferred_directions[self.sel_layer] == "V":
|
||||||
|
self.via_directions = ("H", "H")
|
||||||
|
else:
|
||||||
|
self.via_directions = "pref"
|
||||||
|
|
||||||
self.create_netlist()
|
self.create_netlist()
|
||||||
if not OPTS.netlist_only:
|
if not OPTS.netlist_only:
|
||||||
self.create_layout()
|
self.create_layout()
|
||||||
|
|
||||||
|
def get_bl_name(self):
|
||||||
|
bl_name = self.mux.get_bl_names()
|
||||||
|
return bl_name
|
||||||
|
|
||||||
|
def get_br_name(self, port=0):
|
||||||
|
br_name = self.mux.get_br_names()
|
||||||
|
return br_name
|
||||||
|
|
||||||
def create_netlist(self):
|
def create_netlist(self):
|
||||||
self.add_modules()
|
self.add_modules()
|
||||||
self.add_pins()
|
self.add_pins()
|
||||||
self.create_array()
|
self.create_array()
|
||||||
|
|
||||||
def create_layout(self):
|
def create_layout(self):
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.place_array()
|
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()
|
||||||
self.height = highest.y
|
self.height = highest.y
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
self.add_enclosure(self.mux_inst, "pwell")
|
if "pwell" in layer:
|
||||||
|
self.add_enclosure(self.mux_inst, "pwell")
|
||||||
self.add_boundary()
|
self.add_boundary()
|
||||||
self.DRC_LVS()
|
self.DRC_LVS()
|
||||||
|
|
||||||
def add_pins(self):
|
def add_pins(self):
|
||||||
for i in range(self.columns):
|
for i in range(self.columns):
|
||||||
self.add_pin("bl_{}".format(i))
|
self.add_pin("bl_{}".format(i))
|
||||||
|
|
@ -66,23 +87,19 @@ class single_level_column_mux_array(design.design):
|
||||||
self.add_pin("br_out_{}".format(i))
|
self.add_pin("br_out_{}".format(i))
|
||||||
self.add_pin("gnd")
|
self.add_pin("gnd")
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
def add_modules(self):
|
||||||
self.mux = factory.create(module_type="single_level_column_mux",
|
self.mux = factory.create(module_type="single_level_column_mux",
|
||||||
bitcell_bl=self.bitcell_bl,
|
bitcell_bl=self.bitcell_bl,
|
||||||
bitcell_br=self.bitcell_br)
|
bitcell_br=self.bitcell_br)
|
||||||
self.add_mod(self.mux)
|
self.add_mod(self.mux)
|
||||||
|
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
def setup_layout_constants(self):
|
||||||
self.column_addr_size = num_of_inputs = int(self.words_per_row / 2)
|
self.column_addr_size = int(self.words_per_row / 2)
|
||||||
self.width = self.columns * self.mux.width
|
self.width = self.columns * self.mux.width
|
||||||
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
|
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
|
||||||
# one extra route pitch is to space from the sense amp
|
# one extra route pitch is to space from the sense amp
|
||||||
self.route_height = (self.words_per_row + 3)*self.m1_pitch
|
self.route_height = (self.words_per_row + 3) * self.sel_pitch
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_array(self):
|
def create_array(self):
|
||||||
self.mux_inst = []
|
self.mux_inst = []
|
||||||
# For every column, add a pass gate
|
# For every column, add a pass gate
|
||||||
|
|
@ -90,60 +107,67 @@ class single_level_column_mux_array(design.design):
|
||||||
name = "XMUX{0}".format(col_num)
|
name = "XMUX{0}".format(col_num)
|
||||||
self.mux_inst.append(self.add_inst(name=name,
|
self.mux_inst.append(self.add_inst(name=name,
|
||||||
mod=self.mux))
|
mod=self.mux))
|
||||||
|
|
||||||
self.connect_inst(["bl_{}".format(col_num),
|
self.connect_inst(["bl_{}".format(col_num),
|
||||||
"br_{}".format(col_num),
|
"br_{}".format(col_num),
|
||||||
"bl_out_{}".format(int(col_num/self.words_per_row)),
|
"bl_out_{}".format(int(col_num / self.words_per_row)),
|
||||||
"br_out_{}".format(int(col_num/self.words_per_row)),
|
"br_out_{}".format(int(col_num / self.words_per_row)),
|
||||||
"sel_{}".format(col_num % self.words_per_row),
|
"sel_{}".format(col_num % self.words_per_row),
|
||||||
"gnd"])
|
"gnd"])
|
||||||
|
|
||||||
def place_array(self):
|
def place_array(self):
|
||||||
|
from tech import cell_properties
|
||||||
# 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)
|
xoffset = col_num * self.mux.width
|
||||||
x_off = vector(col_num * self.mux.width, self.route_height)
|
if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2:
|
||||||
self.mux_inst[col_num].place(x_off)
|
mirror = "MY"
|
||||||
|
xoffset = xoffset + self.mux.width
|
||||||
|
else:
|
||||||
|
mirror = ""
|
||||||
|
|
||||||
|
offset = vector(xoffset, self.route_height)
|
||||||
|
self.mux_inst[col_num].place(offset=offset, mirror=mirror)
|
||||||
|
|
||||||
def add_layout_pins(self):
|
def add_layout_pins(self):
|
||||||
""" Add the pins after we determine the height. """
|
""" Add the pins after we determine the height. """
|
||||||
# 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):
|
||||||
mux_inst = self.mux_inst[col_num]
|
mux_inst = self.mux_inst[col_num]
|
||||||
offset = mux_inst.get_pin("bl").ll()
|
bl_pin = mux_inst.get_pin("bl")
|
||||||
|
offset = bl_pin.ll()
|
||||||
self.add_layout_pin(text="bl_{}".format(col_num),
|
self.add_layout_pin(text="bl_{}".format(col_num),
|
||||||
layer="metal2",
|
layer=bl_pin.layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
height=self.height-offset.y)
|
height=self.height - offset.y)
|
||||||
|
|
||||||
offset = mux_inst.get_pin("br").ll()
|
br_pin = mux_inst.get_pin("br")
|
||||||
|
offset = br_pin.ll()
|
||||||
self.add_layout_pin(text="br_{}".format(col_num),
|
self.add_layout_pin(text="br_{}".format(col_num),
|
||||||
layer="metal2",
|
layer=br_pin.layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
height=self.height-offset.y)
|
height=self.height - offset.y)
|
||||||
|
|
||||||
for inst in self.mux_inst:
|
for inst in self.mux_inst:
|
||||||
self.copy_layout_pin(inst, "gnd")
|
self.copy_layout_pin(inst, "gnd")
|
||||||
|
|
||||||
|
|
||||||
def add_routing(self):
|
def add_routing(self):
|
||||||
self.add_horizontal_input_rail()
|
self.add_horizontal_input_rail()
|
||||||
self.add_vertical_poly_rail()
|
self.add_vertical_poly_rail()
|
||||||
self.route_bitlines()
|
self.route_bitlines()
|
||||||
|
|
||||||
def add_horizontal_input_rail(self):
|
def add_horizontal_input_rail(self):
|
||||||
""" Create address input rails on M1 below the mux transistors """
|
""" Create address input rails below the mux transistors """
|
||||||
for j in range(self.words_per_row):
|
for j in range(self.words_per_row):
|
||||||
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
|
offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch)
|
||||||
self.add_layout_pin(text="sel_{}".format(j),
|
self.add_layout_pin(text="sel_{}".format(j),
|
||||||
layer="metal1",
|
layer=self.sel_layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
width=self.mux.width * self.columns)
|
width=self.mux.width * self.columns)
|
||||||
|
|
||||||
def add_vertical_poly_rail(self):
|
def add_vertical_poly_rail(self):
|
||||||
""" Connect the poly to the address rails """
|
""" Connect the poly to the address rails """
|
||||||
|
|
||||||
# Offset to the first transistor gate in the pass gate
|
# Offset to the first transistor gate in the pass gate
|
||||||
for col in range(self.columns):
|
for col in range(self.columns):
|
||||||
# which select bit should this column connect to depends on the position in the word
|
# which select bit should this column connect to depends on the position in the word
|
||||||
|
|
@ -151,68 +175,65 @@ class single_level_column_mux_array(design.design):
|
||||||
# Add the column x offset to find the right select bit
|
# 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,
|
||||||
# Add the poly contact with a shift to account for the rotation
|
self.get_pin("sel_{}".format(sel_index)).cy())
|
||||||
self.add_via_center(layers=("metal1", "contact", "poly"),
|
self.add_via_stack_center(from_layer="poly",
|
||||||
offset=offset)
|
to_layer=self.sel_layer,
|
||||||
|
offset=offset,
|
||||||
|
directions=self.via_directions)
|
||||||
self.add_path("poly", [offset, gate_offset])
|
self.add_path("poly", [offset, gate_offset])
|
||||||
|
|
||||||
def route_bitlines(self):
|
def route_bitlines(self):
|
||||||
""" Connect the output bit-lines to form the appropriate width mux """
|
""" Connect the output bit-lines to form the appropriate width mux """
|
||||||
for j in range(self.columns):
|
for j in range(self.columns):
|
||||||
bl_offset = self.mux_inst[j].get_pin("bl_out").bc()
|
|
||||||
br_offset = self.mux_inst[j].get_pin("br_out").bc()
|
|
||||||
|
|
||||||
bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch)
|
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
|
||||||
br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch)
|
br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
|
||||||
|
|
||||||
bl_out_offset_end = bl_out_offset + vector(0,self.route_height)
|
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||||
br_out_offset_end = br_out_offset + vector(0,self.route_height)
|
br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||||
|
|
||||||
if (j % self.words_per_row) == 0:
|
# Add the horizontal wires for the first bit
|
||||||
# Create the metal1 to connect the n-way mux output from the pass gate
|
if j % self.words_per_row == 0:
|
||||||
# These will be located below the select lines. Yes, these are M2 width
|
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
|
||||||
# to ensure vias are enclosed and M1 min width rules.
|
br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
|
||||||
width = self.m2_width + self.mux.width * (self.words_per_row - 1)
|
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||||
self.add_path("metal1", [bl_out_offset, bl_out_offset+vector(width,0)])
|
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||||
self.add_path("metal1", [br_out_offset, br_out_offset+vector(width,0)])
|
|
||||||
|
self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
|
||||||
|
self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
|
||||||
|
|
||||||
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
|
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
|
||||||
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j/self.words_per_row)),
|
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
|
||||||
layer="metal2",
|
layer=self.bitline_layer,
|
||||||
start=bl_out_offset,
|
start=bl_offset_begin,
|
||||||
end=bl_out_offset_end)
|
end=bl_out_offset_begin)
|
||||||
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j/self.words_per_row)),
|
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)),
|
||||||
layer="metal2",
|
layer=self.bitline_layer,
|
||||||
start=br_out_offset,
|
start=br_offset_begin,
|
||||||
end=br_out_offset_end)
|
end=br_out_offset_begin)
|
||||||
|
|
||||||
|
|
||||||
# This via is on the right of the wire
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=bl_out_offset)
|
|
||||||
|
|
||||||
# This via is on the left of the wire
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=br_out_offset)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
|
||||||
self.add_path("metal2", [ bl_out_offset, bl_out_offset_end])
|
self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin])
|
||||||
self.add_path("metal2", [ br_out_offset, br_out_offset_end])
|
|
||||||
|
# This via is on the right of the wire
|
||||||
# This via is on the right of the wire
|
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
to_layer=self.sel_layer,
|
||||||
offset=bl_out_offset)
|
offset=bl_out_offset_begin,
|
||||||
# This via is on the left of the wire
|
directions=self.via_directions)
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=br_out_offset)
|
# This via is on the left of the wire
|
||||||
|
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||||
|
to_layer=self.sel_layer,
|
||||||
|
offset=br_out_offset_begin,
|
||||||
|
directions=self.via_directions)
|
||||||
|
|
||||||
def get_drain_cin(self):
|
def get_drain_cin(self):
|
||||||
"""Get the relative capacitance of the drain of the NMOS pass TX"""
|
"""Get the relative capacitance of the drain of the NMOS pass TX"""
|
||||||
from tech import parameter
|
from tech import parameter
|
||||||
#Bitcell drain load being used to estimate mux NMOS drain load
|
# Bitcell drain load being used to estimate mux NMOS drain load
|
||||||
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
||||||
return drain_load
|
return drain_load
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,14 @@ class tri_gate_array(design.design):
|
||||||
|
|
||||||
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="m2",
|
||||||
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="m2",
|
||||||
offset=out_pin.ll(),
|
offset=out_pin.ll(),
|
||||||
width=out_pin.width(),
|
width=out_pin.width(),
|
||||||
height=out_pin.height())
|
height=out_pin.height())
|
||||||
|
|
@ -100,24 +100,24 @@ class tri_gate_array(design.design):
|
||||||
for n in ["vdd", "gnd"]:
|
for n in ["vdd", "gnd"]:
|
||||||
for supply_pin in self.tri_inst[i].get_pins(n):
|
for supply_pin in self.tri_inst[i].get_pins(n):
|
||||||
pin_pos = supply_pin.center()
|
pin_pos = supply_pin.center()
|
||||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
self.add_via_center(layers=self.m2_stack,
|
||||||
offset=pin_pos)
|
offset=pin_pos)
|
||||||
self.add_layout_pin_rect_center(text=n,
|
self.add_layout_pin_rect_center(text=n,
|
||||||
layer="metal3",
|
layer="m3",
|
||||||
offset=pin_pos)
|
offset=pin_pos)
|
||||||
|
|
||||||
|
|
||||||
width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width
|
width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width
|
||||||
en_pin = self.tri_inst[0].get_pin("en")
|
en_pin = self.tri_inst[0].get_pin("en")
|
||||||
self.add_layout_pin(text="en",
|
self.add_layout_pin(text="en",
|
||||||
layer="metal1",
|
layer="m1",
|
||||||
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_m1"))
|
||||||
|
|
||||||
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="m1",
|
||||||
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_m1"))
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
# See LICENSE for licensing information.
|
|
||||||
#
|
|
||||||
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
|
||||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
||||||
# (acting for and on behalf of Oklahoma State University)
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
from tech import drc, parameter
|
|
||||||
import debug
|
|
||||||
import design
|
|
||||||
import contact
|
|
||||||
from math import log
|
|
||||||
from math import sqrt
|
|
||||||
import math
|
|
||||||
from vector import vector
|
|
||||||
from sram_factory import factory
|
|
||||||
from globals import OPTS
|
|
||||||
|
|
||||||
class wordline_driver(design.design):
|
|
||||||
"""
|
|
||||||
Creates a Wordline Driver
|
|
||||||
Generates the wordline-driver to drive the bitcell
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, rows, cols):
|
|
||||||
design.design.__init__(self, name)
|
|
||||||
debug.info(1, "Creating {0}".format(self.name))
|
|
||||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
|
||||||
|
|
||||||
self.rows = rows
|
|
||||||
self.cols = cols
|
|
||||||
|
|
||||||
self.create_netlist()
|
|
||||||
if not OPTS.netlist_only:
|
|
||||||
self.create_layout()
|
|
||||||
|
|
||||||
def create_netlist(self):
|
|
||||||
self.add_modules()
|
|
||||||
self.add_pins()
|
|
||||||
self.create_drivers()
|
|
||||||
|
|
||||||
def create_layout(self):
|
|
||||||
self.place_drivers()
|
|
||||||
self.route_layout()
|
|
||||||
self.route_vdd_gnd()
|
|
||||||
self.offset_all_coordinates()
|
|
||||||
self.add_boundary()
|
|
||||||
self.DRC_LVS()
|
|
||||||
|
|
||||||
def add_pins(self):
|
|
||||||
# inputs to wordline_driver.
|
|
||||||
for i in range(self.rows):
|
|
||||||
self.add_pin("in_{0}".format(i), "INPUT")
|
|
||||||
# Outputs from wordline_driver.
|
|
||||||
for i in range(self.rows):
|
|
||||||
self.add_pin("wl_{0}".format(i), "OUTPUT")
|
|
||||||
self.add_pin("en", "INPUT")
|
|
||||||
self.add_pin("vdd", "POWER")
|
|
||||||
self.add_pin("gnd", "GROUND")
|
|
||||||
|
|
||||||
|
|
||||||
def add_modules(self):
|
|
||||||
# This is just used for measurements,
|
|
||||||
# so don't add the module
|
|
||||||
|
|
||||||
self.inv = factory.create(module_type="pdriver",
|
|
||||||
fanout=self.cols,
|
|
||||||
neg_polarity=True)
|
|
||||||
self.add_mod(self.inv)
|
|
||||||
|
|
||||||
self.inv_no_output = factory.create(module_type="pinv",
|
|
||||||
route_output=False)
|
|
||||||
self.add_mod(self.inv_no_output)
|
|
||||||
|
|
||||||
self.nand2 = factory.create(module_type="pnand2")
|
|
||||||
self.add_mod(self.nand2)
|
|
||||||
|
|
||||||
|
|
||||||
def route_vdd_gnd(self):
|
|
||||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
|
||||||
|
|
||||||
# Find the x offsets for where the vias/pins should be placed
|
|
||||||
a_xoffset = self.nand_inst[0].rx()
|
|
||||||
b_xoffset = self.inv2_inst[0].lx()
|
|
||||||
for num in range(self.rows):
|
|
||||||
# this will result in duplicate polygons for rails, but who cares
|
|
||||||
|
|
||||||
# use the inverter offset even though it will be the nand's too
|
|
||||||
(gate_offset, y_dir) = self.get_gate_offset(0, self.inv.height, num)
|
|
||||||
|
|
||||||
# Route both supplies
|
|
||||||
for name in ["vdd", "gnd"]:
|
|
||||||
supply_pin = self.inv2_inst[num].get_pin(name)
|
|
||||||
|
|
||||||
# Add pins in two locations
|
|
||||||
for xoffset in [a_xoffset, b_xoffset]:
|
|
||||||
pin_pos = vector(xoffset, supply_pin.cy())
|
|
||||||
self.add_power_pin(name, pin_pos)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_drivers(self):
|
|
||||||
self.nand_inst = []
|
|
||||||
self.inv2_inst = []
|
|
||||||
for row in range(self.rows):
|
|
||||||
name_nand = "wl_driver_nand{}".format(row)
|
|
||||||
name_inv2 = "wl_driver_inv{}".format(row)
|
|
||||||
|
|
||||||
# add nand 2
|
|
||||||
self.nand_inst.append(self.add_inst(name=name_nand,
|
|
||||||
mod=self.nand2))
|
|
||||||
self.connect_inst(["en",
|
|
||||||
"in_{0}".format(row),
|
|
||||||
"wl_bar_{0}".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
# add inv2
|
|
||||||
self.inv2_inst.append(self.add_inst(name=name_inv2,
|
|
||||||
mod=self.inv))
|
|
||||||
self.connect_inst(["wl_bar_{0}".format(row),
|
|
||||||
"wl_{0}".format(row),
|
|
||||||
"vdd", "gnd"])
|
|
||||||
|
|
||||||
|
|
||||||
def place_drivers(self):
|
|
||||||
nand2_xoffset = 2*self.m1_width + 5*self.m1_space
|
|
||||||
inv2_xoffset = nand2_xoffset + self.nand2.width
|
|
||||||
|
|
||||||
self.width = inv2_xoffset + self.inv.width
|
|
||||||
self.height = self.inv.height * self.rows
|
|
||||||
|
|
||||||
for row in range(self.rows):
|
|
||||||
if (row % 2):
|
|
||||||
y_offset = self.inv.height*(row + 1)
|
|
||||||
inst_mirror = "MX"
|
|
||||||
else:
|
|
||||||
y_offset = self.inv.height*row
|
|
||||||
inst_mirror = "R0"
|
|
||||||
|
|
||||||
nand2_offset=[nand2_xoffset, y_offset]
|
|
||||||
inv2_offset=[inv2_xoffset, y_offset]
|
|
||||||
|
|
||||||
# add nand 2
|
|
||||||
self.nand_inst[row].place(offset=nand2_offset,
|
|
||||||
mirror=inst_mirror)
|
|
||||||
# add inv2
|
|
||||||
self.inv2_inst[row].place(offset=inv2_offset,
|
|
||||||
mirror=inst_mirror)
|
|
||||||
|
|
||||||
|
|
||||||
def route_layout(self):
|
|
||||||
""" Route all of the signals """
|
|
||||||
|
|
||||||
# Wordline enable connection
|
|
||||||
en_pin=self.add_layout_pin(text="en",
|
|
||||||
layer="metal2",
|
|
||||||
offset=[self.m1_width + 2*self.m1_space,0],
|
|
||||||
width=self.m2_width,
|
|
||||||
height=self.height)
|
|
||||||
|
|
||||||
|
|
||||||
for row in range(self.rows):
|
|
||||||
nand_inst = self.nand_inst[row]
|
|
||||||
inv2_inst = self.inv2_inst[row]
|
|
||||||
|
|
||||||
# en connection
|
|
||||||
a_pin = nand_inst.get_pin("A")
|
|
||||||
a_pos = a_pin.lc()
|
|
||||||
clk_offset = vector(en_pin.bc().x,a_pos.y)
|
|
||||||
self.add_segment_center(layer="metal1",
|
|
||||||
start=clk_offset,
|
|
||||||
end=a_pos)
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=clk_offset)
|
|
||||||
|
|
||||||
# Nand2 out to 2nd inv
|
|
||||||
zr_pos = nand_inst.get_pin("Z").rc()
|
|
||||||
al_pos = inv2_inst.get_pin("A").lc()
|
|
||||||
# ensure the bend is in the middle
|
|
||||||
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
|
|
||||||
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
|
|
||||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
|
||||||
|
|
||||||
# connect the decoder input pin to nand2 B
|
|
||||||
b_pin = nand_inst.get_pin("B")
|
|
||||||
b_pos = b_pin.lc()
|
|
||||||
# needs to move down since B nand input is nearly aligned with A inv input
|
|
||||||
up_or_down = self.m2_space if row%2 else -self.m2_space
|
|
||||||
input_offset = vector(0,b_pos.y + up_or_down)
|
|
||||||
mid_via_offset = vector(clk_offset.x,input_offset.y) + vector(0.5*self.m2_width+self.m2_space+0.5*contact.m1m2.width,0)
|
|
||||||
# must under the clk line in M1
|
|
||||||
self.add_layout_pin_segment_center(text="in_{0}".format(row),
|
|
||||||
layer="metal1",
|
|
||||||
start=input_offset,
|
|
||||||
end=mid_via_offset)
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=mid_via_offset,
|
|
||||||
directions=("V","V"))
|
|
||||||
|
|
||||||
# now connect to the nand2 B
|
|
||||||
self.add_path("metal2", [mid_via_offset, b_pos])
|
|
||||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
|
||||||
offset=b_pos - vector(0.5*contact.m1m2.height,0),
|
|
||||||
directions=("H","H"))
|
|
||||||
|
|
||||||
|
|
||||||
# output each WL on the right
|
|
||||||
wl_offset = inv2_inst.get_pin("Z").rc()
|
|
||||||
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
|
|
||||||
layer="metal1",
|
|
||||||
start=wl_offset,
|
|
||||||
end=wl_offset-vector(self.m1_width,0))
|
|
||||||
|
|
||||||
def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True):
|
|
||||||
"""Follows the clk_buf to a wordline signal adding each stages stage effort to a list"""
|
|
||||||
stage_effort_list = []
|
|
||||||
|
|
||||||
stage1_cout = self.inv.get_cin()
|
|
||||||
stage1 = self.nand2.get_stage_effort(stage1_cout, inp_is_rise)
|
|
||||||
stage_effort_list.append(stage1)
|
|
||||||
last_stage_is_rise = stage1.is_rise
|
|
||||||
|
|
||||||
stage2 = self.inv.get_stage_efforts(external_cout, last_stage_is_rise)
|
|
||||||
stage_effort_list.extend(stage2)
|
|
||||||
|
|
||||||
return stage_effort_list
|
|
||||||
|
|
||||||
def get_wl_en_cin(self):
|
|
||||||
"""Get the relative capacitance of all the enable connections in the bank"""
|
|
||||||
#The enable is connected to a nand2 for every row.
|
|
||||||
total_cin = self.nand2.get_cin() * self.rows
|
|
||||||
return total_cin
|
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
# See LICENSE for licensing information.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||||
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||||
|
# (acting for and on behalf of Oklahoma State University)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
import debug
|
||||||
|
import design
|
||||||
|
from tech import drc, layer
|
||||||
|
from vector import vector
|
||||||
|
from sram_factory import factory
|
||||||
|
from globals import OPTS
|
||||||
|
|
||||||
|
class wordline_driver_array(design.design):
|
||||||
|
"""
|
||||||
|
Creates a Wordline Driver
|
||||||
|
Generates the wordline-driver to drive the bitcell
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, rows, cols):
|
||||||
|
design.design.__init__(self, name)
|
||||||
|
debug.info(1, "Creating {0}".format(self.name))
|
||||||
|
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
||||||
|
|
||||||
|
self.rows = rows
|
||||||
|
self.cols = cols
|
||||||
|
|
||||||
|
self.create_netlist()
|
||||||
|
if not OPTS.netlist_only:
|
||||||
|
self.create_layout()
|
||||||
|
|
||||||
|
def create_netlist(self):
|
||||||
|
self.add_modules()
|
||||||
|
self.add_pins()
|
||||||
|
self.create_drivers()
|
||||||
|
|
||||||
|
def create_layout(self):
|
||||||
|
if "li" in layer:
|
||||||
|
self.route_layer = "li"
|
||||||
|
else:
|
||||||
|
self.route_layer = "m1"
|
||||||
|
self.place_drivers()
|
||||||
|
self.route_layout()
|
||||||
|
self.route_vdd_gnd()
|
||||||
|
self.offset_all_coordinates()
|
||||||
|
self.add_boundary()
|
||||||
|
self.DRC_LVS()
|
||||||
|
|
||||||
|
def add_pins(self):
|
||||||
|
# inputs to wordline_driver.
|
||||||
|
for i in range(self.rows):
|
||||||
|
self.add_pin("in_{0}".format(i), "INPUT")
|
||||||
|
# Outputs from wordline_driver.
|
||||||
|
for i in range(self.rows):
|
||||||
|
self.add_pin("wl_{0}".format(i), "OUTPUT")
|
||||||
|
self.add_pin("en", "INPUT")
|
||||||
|
self.add_pin("vdd", "POWER")
|
||||||
|
self.add_pin("gnd", "GROUND")
|
||||||
|
|
||||||
|
def add_modules(self):
|
||||||
|
self.wl_driver = factory.create(module_type="wordline_driver",
|
||||||
|
size=self.cols)
|
||||||
|
self.add_mod(self.wl_driver)
|
||||||
|
|
||||||
|
def route_vdd_gnd(self):
|
||||||
|
"""
|
||||||
|
Add a pin for each row of vdd/gnd which
|
||||||
|
are must-connects next level up.
|
||||||
|
"""
|
||||||
|
if OPTS.tech_name == "sky130":
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
supply_pins = self.wld_inst[0].get_pins(name)
|
||||||
|
for pin in supply_pins:
|
||||||
|
self.add_layout_pin_segment_center(text=name,
|
||||||
|
layer=pin.layer,
|
||||||
|
start=pin.bc(),
|
||||||
|
end=vector(pin.cx(), self.height))
|
||||||
|
else:
|
||||||
|
# Find the x offsets for where the vias/pins should be placed
|
||||||
|
xoffset_list = [self.wld_inst[0].rx()]
|
||||||
|
for num in range(self.rows):
|
||||||
|
# this will result in duplicate polygons for rails, but who cares
|
||||||
|
|
||||||
|
# use the inverter offset even though it will be the and's too
|
||||||
|
(gate_offset, y_dir) = self.get_gate_offset(0,
|
||||||
|
self.wl_driver.height,
|
||||||
|
num)
|
||||||
|
# Route both supplies
|
||||||
|
for name in ["vdd", "gnd"]:
|
||||||
|
supply_pin = self.wld_inst[num].get_pin(name)
|
||||||
|
|
||||||
|
# Add pins in two locations
|
||||||
|
for xoffset in xoffset_list:
|
||||||
|
pin_pos = vector(xoffset, supply_pin.cy())
|
||||||
|
self.add_power_pin(name, pin_pos)
|
||||||
|
|
||||||
|
def create_drivers(self):
|
||||||
|
self.wld_inst = []
|
||||||
|
for row in range(self.rows):
|
||||||
|
name_and = "wl_driver_and{}".format(row)
|
||||||
|
|
||||||
|
# add and2
|
||||||
|
self.wld_inst.append(self.add_inst(name=name_and,
|
||||||
|
mod=self.wl_driver))
|
||||||
|
self.connect_inst(["in_{0}".format(row),
|
||||||
|
"en",
|
||||||
|
"wl_{0}".format(row),
|
||||||
|
"vdd", "gnd"])
|
||||||
|
|
||||||
|
def place_drivers(self):
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
if (row % 2):
|
||||||
|
y_offset = self.wl_driver.height * (row + 1)
|
||||||
|
inst_mirror = "MX"
|
||||||
|
else:
|
||||||
|
y_offset = self.wl_driver.height * row
|
||||||
|
inst_mirror = "R0"
|
||||||
|
|
||||||
|
and2_offset = [self.wl_driver.width, y_offset]
|
||||||
|
|
||||||
|
# add and2
|
||||||
|
self.wld_inst[row].place(offset=and2_offset,
|
||||||
|
mirror=inst_mirror)
|
||||||
|
|
||||||
|
# Leave a well gap to separate the bitcell array well from this well
|
||||||
|
well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active")
|
||||||
|
self.width = self.wl_driver.width + well_gap
|
||||||
|
self.height = self.wl_driver.height * self.rows
|
||||||
|
|
||||||
|
def route_layout(self):
|
||||||
|
""" Route all of the signals """
|
||||||
|
|
||||||
|
# Wordline enable connection
|
||||||
|
en_pin = self.wld_inst[0].get_pin("B")
|
||||||
|
en_bottom_pos = vector(en_pin.lx(), 0)
|
||||||
|
en_pin = self.add_layout_pin(text="en",
|
||||||
|
layer="m2",
|
||||||
|
offset=en_bottom_pos,
|
||||||
|
height=self.height)
|
||||||
|
|
||||||
|
for row in range(self.rows):
|
||||||
|
and_inst = self.wld_inst[row]
|
||||||
|
|
||||||
|
# Drop a via
|
||||||
|
b_pin = and_inst.get_pin("B")
|
||||||
|
self.add_via_stack_center(from_layer=b_pin.layer,
|
||||||
|
to_layer="m2",
|
||||||
|
offset=b_pin.center())
|
||||||
|
|
||||||
|
# connect the decoder input pin to and2 A
|
||||||
|
self.copy_layout_pin(and_inst, "A", "in_{0}".format(row))
|
||||||
|
|
||||||
|
# output each WL on the right
|
||||||
|
wl_offset = and_inst.get_pin("Z").rc()
|
||||||
|
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
|
||||||
|
layer=self.route_layer,
|
||||||
|
start=wl_offset,
|
||||||
|
end=wl_offset - vector(self.m1_width, 0))
|
||||||
|
|
||||||
|
def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True):
|
||||||
|
"""
|
||||||
|
Follows the clk_buf to a wordline signal adding
|
||||||
|
each stages stage effort to a list.
|
||||||
|
"""
|
||||||
|
stage_effort_list = []
|
||||||
|
|
||||||
|
stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise)
|
||||||
|
stage_effort_list.append(stage1)
|
||||||
|
|
||||||
|
return stage_effort_list
|
||||||
|
|
||||||
|
def get_wl_en_cin(self):
|
||||||
|
"""
|
||||||
|
Get the relative capacitance of all
|
||||||
|
the enable connections in the bank
|
||||||
|
"""
|
||||||
|
# The enable is connected to a and2 for every row.
|
||||||
|
total_cin = self.wl_driver.get_cin() * self.rows
|
||||||
|
return total_cin
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue