mirror of https://github.com/VLSIDA/OpenRAM.git
fix merge conflicts
This commit is contained in:
commit
df4a231c04
|
|
@ -1,8 +1,12 @@
|
|||
.DS_Store
|
||||
*~
|
||||
*.orig
|
||||
*.rej
|
||||
*.pyc
|
||||
*.aux
|
||||
*.out
|
||||
*.toc
|
||||
*.synctex.gz
|
||||
**/model_data
|
||||
**/model_data
|
||||
outputs
|
||||
technology/freepdk45/ncsu_basekit
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
before_script:
|
||||
- . /home/gitlab-runner/setup-paths.sh
|
||||
- export OPENRAM_HOME="`pwd`/compiler"
|
||||
- export OPENRAM_TECH="`pwd`/technology"
|
||||
- export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech"
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
|
@ -25,6 +25,15 @@ scn4m_subm:
|
|||
- .coverage.*
|
||||
expire_in: 1 week
|
||||
|
||||
# s8:
|
||||
# stage: test
|
||||
# script:
|
||||
# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8
|
||||
# artifacts:
|
||||
# paths:
|
||||
# - .coverage.*
|
||||
# expire_in: 1 week
|
||||
|
||||
coverage:
|
||||
stage: coverage
|
||||
script:
|
||||
|
|
|
|||
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
|
||||
on my Mac, but you can also use Calibre, Magic, etc.
|
||||
|
||||
1. Calibre
|
||||
1. Klayout
|
||||
|
||||
You can view the designs in [Klayout](https://www.klayout.de/) with the configuration
|
||||
file provided in the tech directories. For example,
|
||||
```
|
||||
klayout temp.gds -l /home/vagrant/openram/technology/freepdk45/tf/FreePDK45.lyp
|
||||
```
|
||||
|
||||
2. Calibre
|
||||
|
||||
Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file:
|
||||
```
|
||||
|
|
@ -52,10 +60,9 @@ on my Mac, but you can also use Calibre, Magic, etc.
|
|||
|
||||
In the viewer ">" opens the layout down a level.
|
||||
|
||||
2. Glade
|
||||
3. Glade
|
||||
|
||||
You can view errors in Glade as well. I like this because it is on my laptop.
|
||||
You can get it from: http://www.peardrop.co.uk/glade/
|
||||
You can view errors in [Glade](http://www.peardrop.co.uk/glade/) as well.
|
||||
|
||||
To remote display over X windows, you need to disable OpenGL acceleration or use vnc
|
||||
or something. You can disable by adding this to your .bashrc in bash:
|
||||
|
|
@ -82,16 +89,16 @@ ui().importCds("default",
|
|||
To load the errors, you simply do Verify->Import Calibre Errors select
|
||||
the .results file from Calibre.
|
||||
|
||||
3. Magic
|
||||
4. Magic
|
||||
|
||||
Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules
|
||||
and Magic from: http://opencircuitdesign.com/
|
||||
and [Magic](http://opencircuitdesign.com/)
|
||||
|
||||
When running DRC or extraction, OpenRAM will load the GDS file, save
|
||||
the .ext/.mag files, and export an extracted netlist (.spice).
|
||||
|
||||
4. It is possible to use other viewers as well, such as:
|
||||
* LayoutEditor http://www.layouteditor.net/
|
||||
5. It is possible to use other viewers as well, such as:
|
||||
* [LayoutEditor](http://www.layouteditor.net/)
|
||||
|
||||
|
||||
# Example to output/input .gds layout files from/to Cadence
|
||||
|
|
|
|||
20
LICENSE
20
LICENSE
|
|
@ -1,23 +1,21 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019 Regents of the University of California and The Board
|
||||
of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
(acting for and on behalf of Oklahoma State University)
|
||||
Copyright (c) 2019, Regents of the University of California and The Board of Regents for the Oklahoma Agricultural and Mechanical College (acting for and on behalf of Oklahoma State University)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
|
|
|
|||
57
README.md
57
README.md
|
|
@ -18,7 +18,7 @@ An open-source static random access memory (SRAM) compiler.
|
|||
# What is OpenRAM?
|
||||
<img align="right" width="25%" src="images/SCMOS_16kb_sram.jpg">
|
||||
|
||||
OpenRAM is an open-source Python framework to create the layout,
|
||||
OpenRAM is an award winning open-source Python framework to create the layout,
|
||||
netlists, timing and power models, placement and routing models, and
|
||||
other views necessary to use SRAMs in ASIC design. OpenRAM supports
|
||||
integration in both commercial and open-source flows with both
|
||||
|
|
@ -33,15 +33,6 @@ things that need to be fixed.
|
|||
|
||||
# Basic Setup
|
||||
|
||||
## Docker Image
|
||||
|
||||
We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
|
||||
available that has all tools installed for the [SCMOS] process. It is
|
||||
available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
|
||||
Please see
|
||||
[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
|
||||
for information on how to use this docker image.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The OpenRAM compiler has very few dependencies:
|
||||
|
|
@ -88,6 +79,23 @@ You may get the entire [FreePDK45 PDK here][FreePDK45].
|
|||
If you are using [SCMOS], you should install [Magic] and [Netgen].
|
||||
We have included the most recent SCN4M_SUBM design rules from [Qflow].
|
||||
|
||||
## Docker Image
|
||||
|
||||
We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
|
||||
available that has all tools installed for the [SCMOS] process. It is
|
||||
available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
|
||||
Please see
|
||||
[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
|
||||
for information on how to use this docker image.
|
||||
|
||||
## Vagrant Image
|
||||
|
||||
We have a pre-configured Ubuntu [Vagrant](https://www.vagrantup.com/) image
|
||||
available that has all tools installed for the [SCMOS] process.
|
||||
Please see
|
||||
[our README.md](https://github.com/VLSIDA/openram-vagrant-image/blob/master/README.md)
|
||||
for information on how to use this image.
|
||||
|
||||
# Basic Usage
|
||||
|
||||
Once you have defined the environment, you can run OpenRAM from the command line
|
||||
|
|
@ -104,12 +112,16 @@ num_words = 16
|
|||
|
||||
# Technology to use in $OPENRAM_TECH
|
||||
tech_name = "scn4m_subm"
|
||||
|
||||
# You can use the technology nominal corner only
|
||||
nominal_corner_only = True
|
||||
# Or you can specify particular corners
|
||||
# Process corners to characterize
|
||||
process_corners = ["TT"]
|
||||
# process_corners = ["SS", "TT", "FF"]
|
||||
# Voltage corners to characterize
|
||||
supply_voltages = [ 3.3 ]
|
||||
# supply_voltages = [ 3.0, 3.3, 3.5 ]
|
||||
# Temperature corners to characterize
|
||||
temperatures = [ 25 ]
|
||||
# temperatures = [ 0, 25 100]
|
||||
|
||||
# Output directory for the results
|
||||
output_path = "temp"
|
||||
|
|
@ -119,11 +131,6 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
|||
# Disable analytical models for full characterization (WARNING: slow!)
|
||||
# analytical_delay = False
|
||||
|
||||
# To force this to use magic and netgen for DRC/LVS/PEX
|
||||
# Could be calibre for FreePDK45
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
```
|
||||
|
||||
You can then run OpenRAM by executing:
|
||||
|
|
@ -188,7 +195,7 @@ specific technology (e.g., [FreePDK45]) should be a subdirectory
|
|||
+ Report bugs by submitting [Github issues].
|
||||
+ Develop new features (see [how to contribute](./CONTRIBUTING.md))
|
||||
+ Submit code/fixes using a [Github pull request]
|
||||
+ Follow our [project][Github projects].
|
||||
+ Follow our [project][Github project].
|
||||
+ Read and cite our [ICCAD paper][OpenRAMpaper]
|
||||
|
||||
# Further Help
|
||||
|
|
@ -207,15 +214,7 @@ OpenRAM is licensed under the [BSD 3-clause License](./LICENSE).
|
|||
|
||||
- [Matthew Guthaus] from [VLSIDA] created the OpenRAM project and is the lead architect.
|
||||
- [James Stine] from [VLSIARCH] co-founded the project.
|
||||
- Hunter Nichols maintains and updates the timing characterization.
|
||||
- Michael Grimes created and maintains the multiport netlist code.
|
||||
- Jennifer Sowash is creating the OpenRAM IP library.
|
||||
- Jesse Cirimelli-Low created the datasheet generation.
|
||||
- Samira Ataei created early multi-bank layouts and control logic.
|
||||
- Bin Wu created early parameterized cells.
|
||||
- Yusu Wang is porting parameterized cells to new technologies.
|
||||
- Brian Chen created early prototypes of the timing characterizer.
|
||||
- Jeff Butera created early prototypes of the bank layout.
|
||||
- Many students: Hunter Nichols, Michael Grimes, Jennifer Sowash, Yusu Wang, Joey Kunzler, Jesse Cirimelli-Low, Samira Ataei, Bin Wu, Brian Chen, Jeff Butera
|
||||
|
||||
If I forgot to add you, please let me know!
|
||||
|
||||
|
|
@ -229,7 +228,7 @@ If I forgot to add you, please let me know!
|
|||
|
||||
[Github issues]: https://github.com/VLSIDA/OpenRAM/issues
|
||||
[Github pull request]: https://github.com/VLSIDA/OpenRAM/pulls
|
||||
[Github projects]: https://github.com/VLSIDA/OpenRAM/projects
|
||||
[Github project]: https://github.com/VLSIDA/OpenRAM
|
||||
|
||||
[documentation]: https://docs.google.com/presentation/d/10InGB33N51I6oBHnqpU7_w9DXlx-qe9zdrlco2Yc5co/edit?usp=sharing
|
||||
[dev-group]: mailto:openram-dev-group@ucsc.edu
|
||||
|
|
|
|||
|
|
@ -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 debug
|
||||
from tech import drc, layer
|
||||
import tech
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
import sys
|
||||
|
||||
|
||||
class contact(hierarchy_design.hierarchy_design):
|
||||
|
|
@ -26,22 +29,42 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"),
|
||||
def __init__(self, layer_stack, dimensions=(1, 1), directions=None,
|
||||
implant_type=None, well_type=None, name=""):
|
||||
# This will ignore the name parameter since
|
||||
# we can guarantee a unique name here
|
||||
|
||||
hierarchy_design.hierarchy_design.__init__(self, name)
|
||||
debug.info(4, "create contact object {0}".format(name))
|
||||
|
||||
self.add_comment("layers: {0}".format(layer_stack))
|
||||
self.add_comment("dimensions: {0}".format(dimensions))
|
||||
if implant_type or well_type:
|
||||
self.add_comment("implant type: {}\n".format(implant_type))
|
||||
self.add_comment("well_type: {}\n".format(well_type))
|
||||
|
||||
|
||||
self.is_well_contact = implant_type == well_type
|
||||
|
||||
# If we have a special tap layer, use it
|
||||
self.layer_stack = layer_stack
|
||||
self.dimensions = dimensions
|
||||
self.directions = directions
|
||||
|
||||
# Non-preferred directions
|
||||
if directions == "nonpref":
|
||||
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
|
||||
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
|
||||
self.directions = (first_dir, second_dir)
|
||||
# Preferred directions
|
||||
elif directions == "pref":
|
||||
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||
tech.preferred_directions[layer_stack[2]])
|
||||
# User directions
|
||||
elif directions:
|
||||
self.directions = directions
|
||||
# Preferred directions
|
||||
else:
|
||||
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||
tech.preferred_directions[layer_stack[2]])
|
||||
self.offset = vector(0, 0)
|
||||
self.implant_type = implant_type
|
||||
self.well_type = well_type
|
||||
|
|
@ -56,30 +79,39 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
self.create_contact_array()
|
||||
self.create_first_layer_enclosure()
|
||||
self.create_second_layer_enclosure()
|
||||
self.create_nitride_cut_enclosure()
|
||||
|
||||
self.height = max(obj.offset.y + obj.height for obj in self.objs)
|
||||
self.width = max(obj.offset.x + obj.width for obj in self.objs)
|
||||
self.height = max(self.first_layer_position.y + self.first_layer_height,
|
||||
self.second_layer_position.y + self.second_layer_height)
|
||||
self.width = max(self.first_layer_position.x + self.first_layer_width,
|
||||
self.second_layer_position.x + self.second_layer_width)
|
||||
|
||||
# Do not include the select layer in the height/width
|
||||
if self.implant_type and self.well_type:
|
||||
self.create_implant_well_enclosures()
|
||||
elif self.implant_type or self.well_type:
|
||||
debug.error(-1, "Must define both implant and well type or none at all.")
|
||||
debug.error(-1,
|
||||
"Must define both implant and well type or none.")
|
||||
|
||||
def setup_layers(self):
|
||||
""" Locally assign the layer names. """
|
||||
|
||||
(first_layer, via_layer, second_layer) = self.layer_stack
|
||||
self.first_layer_name = first_layer
|
||||
self.via_layer_name = via_layer
|
||||
# Some technologies have a separate active
|
||||
# contact from the poly contact
|
||||
# We will use contact for DRC, but active_contact for output
|
||||
if first_layer == "active" or second_layer == "active":
|
||||
self.via_layer_name_expanded = "active_" + via_layer
|
||||
else:
|
||||
self.via_layer_name_expanded = via_layer
|
||||
self.second_layer_name = second_layer
|
||||
|
||||
# Contacts will have unique per first layer
|
||||
if via_layer in tech.layer:
|
||||
self.via_layer_name = via_layer
|
||||
elif via_layer == "contact":
|
||||
if first_layer in ("active", "poly"):
|
||||
self.via_layer_name = first_layer + "_" + via_layer
|
||||
elif second_layer in ("active", "poly"):
|
||||
self.via_layer_name = second_layer + "_" + via_layer
|
||||
else:
|
||||
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||
else:
|
||||
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||
|
||||
def setup_layout_constants(self):
|
||||
""" Determine the design rules for the enclosure layers """
|
||||
|
|
@ -95,70 +127,105 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
# The extend rule applies to asymmetric enclosures in one direction.
|
||||
# The enclosure rule applies to symmetric enclosure component.
|
||||
|
||||
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||
first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||
self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
# If there's a different rule for active
|
||||
# FIXME: Make this more elegant
|
||||
if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys():
|
||||
self.first_layer_extend = drc("tap_extend_contact")
|
||||
else:
|
||||
self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
|
||||
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||
second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||
self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
|
||||
# In some technologies, the minimum width may be larger
|
||||
# than the overlap requirement around the via, so
|
||||
# check this for each dimension.
|
||||
if self.directions[0] == "V":
|
||||
self.first_layer_horizontal_enclosure = max(first_layer_enclosure,
|
||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(first_layer_extend,
|
||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure,
|
||||
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(self.first_layer_extend,
|
||||
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||
elif self.directions[0] == "H":
|
||||
self.first_layer_horizontal_enclosure = max(first_layer_extend,
|
||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(first_layer_enclosure,
|
||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.first_layer_horizontal_enclosure = max(self.first_layer_extend,
|
||||
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
|
||||
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||
else:
|
||||
debug.error("Invalid first layer direction.", -1)
|
||||
debug.error("Invalid first layer direction: ".format(self.directions[0]), -1)
|
||||
|
||||
# In some technologies, the minimum width may be larger than the overlap requirement around the via, so
|
||||
# In some technologies, the minimum width may be larger
|
||||
# than the overlap requirement around the via, so
|
||||
# check this for each dimension.
|
||||
if self.directions[1] == "V":
|
||||
self.second_layer_horizontal_enclosure = max(second_layer_enclosure,
|
||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_vertical_enclosure = max(second_layer_extend,
|
||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_horizontal_enclosure = max(self.second_layer_enclosure,
|
||||
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_vertical_enclosure = max(self.second_layer_extend,
|
||||
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||
elif self.directions[1] == "H":
|
||||
self.second_layer_horizontal_enclosure = max(second_layer_extend,
|
||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_vertical_enclosure = max(second_layer_enclosure,
|
||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_horizontal_enclosure = max(self.second_layer_extend,
|
||||
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
|
||||
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||
else:
|
||||
debug.error("Invalid second layer direction.", -1)
|
||||
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
|
||||
|
||||
def create_contact_array(self):
|
||||
""" Create the contact array at the origin"""
|
||||
# offset for the via array
|
||||
self.via_layer_position = vector(
|
||||
max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure),
|
||||
max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure))
|
||||
max(self.first_layer_horizontal_enclosure,
|
||||
self.second_layer_horizontal_enclosure),
|
||||
max(self.first_layer_vertical_enclosure,
|
||||
self.second_layer_vertical_enclosure))
|
||||
|
||||
for i in range(self.dimensions[1]):
|
||||
offset = self.via_layer_position + vector(0, self.contact_pitch * i)
|
||||
offset = self.via_layer_position + vector(0,
|
||||
self.contact_pitch * i)
|
||||
for j in range(self.dimensions[0]):
|
||||
self.add_rect(layer=self.via_layer_name_expanded,
|
||||
self.add_rect(layer=self.via_layer_name,
|
||||
offset=offset,
|
||||
width=self.contact_width,
|
||||
height=self.contact_width)
|
||||
offset = offset + vector(self.contact_pitch, 0)
|
||||
|
||||
def create_nitride_cut_enclosure(self):
|
||||
""" Special layer that encloses poly contacts in some processes """
|
||||
# Check if there is a special poly nitride cut layer
|
||||
if "npc" not in tech.layer:
|
||||
return
|
||||
|
||||
npc_enclose_poly = drc("npc_enclose_poly")
|
||||
npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly)
|
||||
# Only add for poly layers
|
||||
if self.first_layer_name == "poly":
|
||||
self.add_rect(layer="npc",
|
||||
offset=self.first_layer_position - npc_enclose_offset,
|
||||
width=self.first_layer_width + 2 * npc_enclose_poly,
|
||||
height=self.first_layer_height + 2 * npc_enclose_poly)
|
||||
elif self.second_layer_name == "poly":
|
||||
self.add_rect(layer="npc",
|
||||
offset=self.second_layer_position - npc_enclose_offset,
|
||||
width=self.second_layer_width + 2 * npc_enclose_poly,
|
||||
height=self.second_layer_height + 2 * npc_enclose_poly)
|
||||
|
||||
def create_first_layer_enclosure(self):
|
||||
# this is if the first and second layers are different
|
||||
self.first_layer_position = vector(
|
||||
max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0),
|
||||
max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0))
|
||||
|
||||
self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure
|
||||
self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure
|
||||
self.add_rect(layer=self.first_layer_name,
|
||||
self.first_layer_width = max(self.contact_array_width + 2 * self.first_layer_horizontal_enclosure,
|
||||
self.first_layer_minwidth)
|
||||
self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure,
|
||||
self.first_layer_minwidth)
|
||||
if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer:
|
||||
first_layer_name = "tap"
|
||||
else:
|
||||
first_layer_name = self.first_layer_name
|
||||
self.add_rect(layer=first_layer_name,
|
||||
offset=self.first_layer_position,
|
||||
width=self.first_layer_width,
|
||||
height=self.first_layer_height)
|
||||
|
|
@ -169,57 +236,72 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0),
|
||||
max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0))
|
||||
|
||||
self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure
|
||||
self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure
|
||||
self.second_layer_width = max(self.contact_array_width + 2 * self.second_layer_horizontal_enclosure,
|
||||
self.second_layer_minwidth)
|
||||
self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure,
|
||||
self.second_layer_minwidth)
|
||||
self.add_rect(layer=self.second_layer_name,
|
||||
offset=self.second_layer_position,
|
||||
width=self.second_layer_width,
|
||||
height=self.second_layer_height)
|
||||
|
||||
def create_implant_well_enclosures(self):
|
||||
implant_position = self.first_layer_position - [drc("implant_enclosure_active")] * 2
|
||||
implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active")
|
||||
implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active")
|
||||
implant_position = self.first_layer_position - [drc("implant_enclose_active")] * 2
|
||||
implant_width = self.first_layer_width + 2 * drc("implant_enclose_active")
|
||||
implant_height = self.first_layer_height + 2 * drc("implant_enclose_active")
|
||||
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||
offset=implant_position,
|
||||
width=implant_width,
|
||||
height=implant_height)
|
||||
well_position = self.first_layer_position - [drc("well_enclosure_active")] * 2
|
||||
well_width = self.first_layer_width + 2 * drc("well_enclosure_active")
|
||||
well_height = self.first_layer_height + 2 * drc("well_enclosure_active")
|
||||
self.add_rect(layer="{}well".format(self.well_type),
|
||||
offset=well_position,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
|
||||
# Optionally implant well if layer exists
|
||||
well_layer = "{}well".format(self.well_type)
|
||||
if well_layer in tech.layer:
|
||||
well_width_rule = drc("minwidth_" + well_layer)
|
||||
self.well_enclose_active = drc(well_layer + "_enclose_active")
|
||||
self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active,
|
||||
well_width_rule)
|
||||
self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active,
|
||||
well_width_rule)
|
||||
center_pos = vector(0.5*self.width, 0.5*self.height)
|
||||
well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height)
|
||||
self.add_rect(layer=well_layer,
|
||||
offset=well_position,
|
||||
width=self.well_width,
|
||||
height=self.well_height)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
""" Get total power of a module """
|
||||
return self.return_power()
|
||||
|
||||
|
||||
from sram_factory import factory
|
||||
# Set up a static for each layer to be used for measurements
|
||||
for layer_stack in tech.layer_stacks:
|
||||
(layer1, via, layer2) = layer_stack
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=layer_stack)
|
||||
module = sys.modules[__name__]
|
||||
# Also create a contact that is just the first layer
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
setattr(module, layer1 + "_contact", cont)
|
||||
else:
|
||||
setattr(module, layer1 + "_via", cont)
|
||||
|
||||
# Set up a static for each well contact for measurements
|
||||
if "nwell" in tech.layer:
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=tech.active_stack,
|
||||
implant_type="n",
|
||||
well_type="n")
|
||||
module = sys.modules[__name__]
|
||||
setattr(module, "nwell_contact", cont)
|
||||
|
||||
if "pwell" in tech.layer:
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=tech.active_stack,
|
||||
implant_type="p",
|
||||
well_type="p")
|
||||
module = sys.modules[__name__]
|
||||
setattr(module, "pwell_contact", cont)
|
||||
|
||||
# This is not instantiated and used for calculations only.
|
||||
# These are static 1x1 contacts to reuse in all the design modules.
|
||||
well = factory.create(module_type="contact",
|
||||
layer_stack=("active", "contact", "metal1"),
|
||||
directions=("H", "V"))
|
||||
active = factory.create(module_type="contact",
|
||||
layer_stack=("active", "contact", "metal1"),
|
||||
directions=("H", "V"))
|
||||
poly = factory.create(module_type="contact",
|
||||
layer_stack=("poly", "contact", "metal1"),
|
||||
directions=("V", "H"))
|
||||
m1m2 = factory.create(module_type="contact",
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
directions=("H", "V"))
|
||||
m2m3 = factory.create(module_type="contact",
|
||||
layer_stack=("metal2", "via2", "metal3"),
|
||||
directions=("V", "H"))
|
||||
if "metal4" in layer.keys():
|
||||
m3m4 = factory.create(module_type="contact",
|
||||
layer_stack=("metal3", "via3", "metal4"),
|
||||
directions=("H", "V"))
|
||||
else:
|
||||
m3m4 = None
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
#
|
||||
from hierarchy_design import hierarchy_design
|
||||
from utils import round_to_grid
|
||||
import contact
|
||||
from globals import OPTS
|
||||
import re
|
||||
|
||||
|
||||
class design(hierarchy_design):
|
||||
"""
|
||||
This is the same as the hierarchy_design class except it contains
|
||||
some DRC constants and analytical models for other modules to reuse.
|
||||
some DRC/layer constants and analytical models for other modules to reuse.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -21,42 +23,195 @@ class design(hierarchy_design):
|
|||
hierarchy_design.__init__(self, name)
|
||||
|
||||
self.setup_drc_constants()
|
||||
self.setup_layer_constants()
|
||||
self.setup_multiport_constants()
|
||||
|
||||
from tech import layer
|
||||
self.m1_pitch = max(contact.m1m2.width, contact.m1m2.height) + max(self.m1_space, self.m2_space)
|
||||
self.m2_pitch = max(contact.m2m3.width, contact.m2m3.height) + max(self.m2_space, self.m3_space)
|
||||
if "metal4" in layer:
|
||||
self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space)
|
||||
def setup_layer_constants(self):
|
||||
"""
|
||||
These are some layer constants used
|
||||
in many places in the compiler.
|
||||
"""
|
||||
|
||||
from tech import layer_indices
|
||||
import tech
|
||||
for layer in layer_indices:
|
||||
key = "{}_stack".format(layer)
|
||||
|
||||
# Set the stack as a local helper
|
||||
try:
|
||||
layer_stack = getattr(tech, key)
|
||||
setattr(self, key, layer_stack)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Skip computing the pitch for active
|
||||
if layer == "active":
|
||||
continue
|
||||
|
||||
# Add the pitch
|
||||
setattr(self,
|
||||
"{}_pitch".format(layer),
|
||||
self.compute_pitch(layer, True))
|
||||
|
||||
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
||||
setattr(self,
|
||||
"{}_nonpref_pitch".format(layer),
|
||||
self.compute_pitch(layer, False))
|
||||
|
||||
if False:
|
||||
from tech import preferred_directions
|
||||
print(preferred_directions)
|
||||
from tech import layer, layer_indices
|
||||
for name in layer_indices:
|
||||
if name == "active":
|
||||
continue
|
||||
try:
|
||||
print("{0} width {1} space {2}".format(name,
|
||||
getattr(self, "{}_width".format(name)),
|
||||
getattr(self, "{}_space".format(name))))
|
||||
|
||||
print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)),
|
||||
getattr(self, "{}_nonpref_pitch".format(name))))
|
||||
except AttributeError:
|
||||
pass
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
def compute_pitch(self, layer, preferred=True):
|
||||
|
||||
"""
|
||||
This is the preferred direction pitch
|
||||
i.e. we take the minimum or maximum contact dimension
|
||||
"""
|
||||
# Find the layer stacks this is used in
|
||||
from tech import layer_stacks
|
||||
pitches = []
|
||||
for stack in layer_stacks:
|
||||
# Compute the pitch with both vias above and below (if they exist)
|
||||
if stack[0] == layer:
|
||||
pitches.append(self.compute_layer_pitch(stack, preferred))
|
||||
if stack[2] == layer:
|
||||
pitches.append(self.compute_layer_pitch(stack[::-1], True))
|
||||
|
||||
return max(pitches)
|
||||
|
||||
def compute_layer_pitch(self, layer_stack, preferred):
|
||||
|
||||
(layer1, via, layer2) = layer_stack
|
||||
try:
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
contact1 = getattr(contact, layer1 + "_contact")
|
||||
else:
|
||||
contact1 = getattr(contact, layer1 + "_via")
|
||||
except AttributeError:
|
||||
contact1 = getattr(contact, layer2 + "_via")
|
||||
|
||||
if preferred:
|
||||
if self.get_preferred_direction(layer1) == "V":
|
||||
contact_width = contact1.first_layer_width
|
||||
else:
|
||||
contact_width = contact1.first_layer_height
|
||||
else:
|
||||
self.m3_pitch = self.m2_pitch
|
||||
if self.get_preferred_direction(layer1) == "V":
|
||||
contact_width = contact1.first_layer_height
|
||||
else:
|
||||
contact_width = contact1.first_layer_width
|
||||
layer_space = getattr(self, layer1 + "_space")
|
||||
|
||||
#print(layer_stack)
|
||||
#print(contact1)
|
||||
pitch = contact_width + layer_space
|
||||
|
||||
return round_to_grid(pitch)
|
||||
|
||||
def setup_drc_constants(self):
|
||||
""" These are some DRC constants used in many places in the compiler."""
|
||||
from tech import drc, layer
|
||||
self.well_width = drc("minwidth_well")
|
||||
self.poly_width = drc("minwidth_poly")
|
||||
self.poly_space = drc("poly_to_poly")
|
||||
self.m1_width = drc("minwidth_metal1")
|
||||
self.m1_space = drc("metal1_to_metal1")
|
||||
self.m2_width = drc("minwidth_metal2")
|
||||
self.m2_space = drc("metal2_to_metal2")
|
||||
self.m3_width = drc("minwidth_metal3")
|
||||
self.m3_space = drc("metal3_to_metal3")
|
||||
if "metal4" in layer:
|
||||
self.m4_width = drc("minwidth_metal4")
|
||||
self.m4_space = drc("metal4_to_metal4")
|
||||
self.active_width = drc("minwidth_active")
|
||||
self.active_space = drc("active_to_body_active")
|
||||
self.contact_width = drc("minwidth_contact")
|
||||
"""
|
||||
These are some DRC constants used in many places
|
||||
in the compiler.
|
||||
"""
|
||||
# Make some local rules for convenience
|
||||
from tech import drc
|
||||
for rule in drc.keys():
|
||||
# Single layer width rules
|
||||
match = re.search(r"minwidth_(.*)", rule)
|
||||
if match:
|
||||
if match.group(1) == "active_contact":
|
||||
setattr(self, "contact_width", drc(match.group(0)))
|
||||
else:
|
||||
setattr(self, match.group(1) + "_width", drc(match.group(0)))
|
||||
|
||||
self.poly_to_active = drc("poly_to_active")
|
||||
self.poly_extend_active = drc("poly_extend_active")
|
||||
self.poly_to_polycontact = drc("poly_to_polycontact")
|
||||
self.contact_to_gate = drc("contact_to_gate")
|
||||
self.well_enclose_active = drc("well_enclosure_active")
|
||||
self.implant_enclose_active = drc("implant_enclosure_active")
|
||||
self.implant_space = drc("implant_to_implant")
|
||||
# Single layer area rules
|
||||
match = re.search(r"minarea_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
# Single layer spacing rules
|
||||
match = re.search(r"(.*)_to_(.*)", rule)
|
||||
if match and match.group(1) == match.group(2):
|
||||
setattr(self, match.group(1) + "_space", drc(match.group(0)))
|
||||
elif match and match.group(1) != match.group(2):
|
||||
if match.group(2) == "poly_active":
|
||||
setattr(self, match.group(1) + "_to_contact",
|
||||
drc(match.group(0)))
|
||||
else:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
match = re.search(r"(.*)_enclose_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
match = re.search(r"(.*)_extend_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
# Create the maximum well extend active that gets used
|
||||
# by cells to extend the wells for interaction with other cells
|
||||
from tech import layer
|
||||
self.well_extend_active = 0
|
||||
if "nwell" in layer:
|
||||
self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active)
|
||||
if "pwell" in layer:
|
||||
self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active)
|
||||
|
||||
# The active offset is due to the well extension
|
||||
if "pwell" in layer:
|
||||
self.pwell_enclose_active = drc("pwell_enclose_active")
|
||||
else:
|
||||
self.pwell_enclose_active = 0
|
||||
if "nwell" in layer:
|
||||
self.nwell_enclose_active = drc("nwell_enclose_active")
|
||||
else:
|
||||
self.nwell_enclose_active = 0
|
||||
# Use the max of either so that the poly gates will align properly
|
||||
self.well_enclose_active = max(self.pwell_enclose_active,
|
||||
self.nwell_enclose_active,
|
||||
self.active_space)
|
||||
|
||||
# These are for debugging previous manual rules
|
||||
if False:
|
||||
print("poly_width", self.poly_width)
|
||||
print("poly_space", self.poly_space)
|
||||
print("m1_width", self.m1_width)
|
||||
print("m1_space", self.m1_space)
|
||||
print("m2_width", self.m2_width)
|
||||
print("m2_space", self.m2_space)
|
||||
print("m3_width", self.m3_width)
|
||||
print("m3_space", self.m3_space)
|
||||
print("m4_width", self.m4_width)
|
||||
print("m4_space", self.m4_space)
|
||||
print("active_width", self.active_width)
|
||||
print("active_space", self.active_space)
|
||||
print("contact_width", self.contact_width)
|
||||
print("poly_to_active", self.poly_to_active)
|
||||
print("poly_extend_active", self.poly_extend_active)
|
||||
print("poly_to_contact", self.poly_to_contact)
|
||||
print("active_contact_to_gate", self.active_contact_to_gate)
|
||||
print("poly_contact_to_gate", self.poly_contact_to_gate)
|
||||
print("well_enclose_active", self.well_enclose_active)
|
||||
print("implant_enclose_active", self.implant_enclose_active)
|
||||
print("implant_space", self.implant_space)
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
coordinate += [[x, y]]
|
||||
return coordinate
|
||||
|
||||
|
||||
def normalize(self):
|
||||
""" Re-find the LL and UR points after a transform """
|
||||
(first, second) = self.boundary
|
||||
|
|
@ -66,14 +66,19 @@ class geometry:
|
|||
def update_boundary(self):
|
||||
""" Update the boundary with a new placement. """
|
||||
self.compute_boundary(self.offset, self.mirror, self.rotate)
|
||||
|
||||
|
||||
def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0):
|
||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance. """
|
||||
"""
|
||||
Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance.
|
||||
"""
|
||||
if OPTS.netlist_only:
|
||||
self.boundary = [vector(0, 0), vector(0, 0)]
|
||||
return
|
||||
|
||||
(ll, ur) = [vector(0, 0), vector(self.width, self.height)]
|
||||
|
||||
# Mirroring is performed before rotation
|
||||
if mirror == "MX":
|
||||
ll = ll.scale(1, -1)
|
||||
ur = ur.scale(1, -1)
|
||||
|
|
@ -83,8 +88,14 @@ class geometry:
|
|||
elif mirror == "XY":
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
|
||||
if rotate == 90:
|
||||
elif mirror == "" or mirror == "R0":
|
||||
pass
|
||||
else:
|
||||
debug.error("Invalid mirroring: {}".format(mirror), -1)
|
||||
|
||||
if rotate == 0:
|
||||
pass
|
||||
elif rotate == 90:
|
||||
ll = ll.rotate_scale(-1, 1)
|
||||
ur = ur.rotate_scale(-1, 1)
|
||||
elif rotate == 180:
|
||||
|
|
@ -93,22 +104,24 @@ class geometry:
|
|||
elif rotate == 270:
|
||||
ll = ll.rotate_scale(1, -1)
|
||||
ur = ur.rotate_scale(1, -1)
|
||||
else:
|
||||
debug.error("Invalid rotation: {}".format(rotate), -1)
|
||||
|
||||
self.boundary = [offset + ll, offset + ur]
|
||||
self.normalize()
|
||||
|
||||
|
||||
def ll(self):
|
||||
""" Return the lower left corner """
|
||||
return self.boundary[0]
|
||||
|
||||
|
||||
def ur(self):
|
||||
""" Return the upper right corner """
|
||||
return self.boundary[1]
|
||||
|
||||
|
||||
def lr(self):
|
||||
""" Return the lower right corner """
|
||||
return vector(self.boundary[1].x, self.boundary[0].y)
|
||||
|
||||
|
||||
def ul(self):
|
||||
""" Return the upper left corner """
|
||||
return vector(self.boundary[0].x, self.boundary[1].y)
|
||||
|
|
@ -132,11 +145,16 @@ class geometry:
|
|||
def cx(self):
|
||||
""" Return the center x """
|
||||
return 0.5 * (self.boundary[0].x + self.boundary[1].x)
|
||||
|
||||
|
||||
def cy(self):
|
||||
""" Return the center y """
|
||||
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
||||
|
||||
def center(self):
|
||||
""" Return the center coordinate """
|
||||
return vector(self.cx(), self.cy())
|
||||
|
||||
|
||||
class instance(geometry):
|
||||
"""
|
||||
An instance of an instance/module with a specified location and
|
||||
|
|
@ -147,7 +165,7 @@ class instance(geometry):
|
|||
geometry.__init__(self)
|
||||
debug.check(mirror not in ["R90", "R180", "R270"],
|
||||
"Please use rotation and not mirroring during instantiation.")
|
||||
|
||||
|
||||
self.name = name
|
||||
self.mod = mod
|
||||
self.gds = mod.gds
|
||||
|
|
@ -165,10 +183,10 @@ class instance(geometry):
|
|||
self.width = round_to_grid(mod.width)
|
||||
self.height = round_to_grid(mod.height)
|
||||
self.compute_boundary(offset, mirror, rotate)
|
||||
|
||||
|
||||
debug.info(4, "creating instance: " + self.name)
|
||||
|
||||
def get_blockages(self, layer, top=False):
|
||||
def get_blockages(self, lpp, top=False):
|
||||
""" Retrieve blockages of all modules in this instance.
|
||||
Apply the transform of the instance placement to give absolute blockages."""
|
||||
angle = math.radians(float(self.rotate))
|
||||
|
|
@ -192,20 +210,19 @@ class instance(geometry):
|
|||
if self.mod.is_library_cell:
|
||||
# Writes library cell blockages as shapes instead of a large metal blockage
|
||||
blockages = []
|
||||
blockages = self.mod.gds.getBlockages(layer)
|
||||
blockages = self.mod.gds.getBlockages(lpp)
|
||||
for b in blockages:
|
||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
||||
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||
else:
|
||||
blockages = self.mod.get_blockages(layer)
|
||||
blockages = self.mod.get_blockages(lpp)
|
||||
for b in blockages:
|
||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
||||
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||
return new_blockages
|
||||
|
||||
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Recursively writes all the sub-modules in this instance"""
|
||||
debug.info(4, "writing instance: " + self.name)
|
||||
# make sure to write out my module/structure
|
||||
# make sure to write out my module/structure
|
||||
# (it will only be written the first time though)
|
||||
self.mod.gds_write_file(self.gds)
|
||||
# now write an instance of my module/structure
|
||||
|
|
@ -214,7 +231,7 @@ class instance(geometry):
|
|||
offsetInMicrons=self.offset,
|
||||
mirror=self.mirror,
|
||||
rotate=self.rotate)
|
||||
|
||||
|
||||
def place(self, offset, mirror="R0", rotate=0):
|
||||
""" This updates the placement of an instance. """
|
||||
# Update the placement of an already added instance
|
||||
|
|
@ -222,27 +239,27 @@ class instance(geometry):
|
|||
self.mirror = mirror
|
||||
self.rotate = rotate
|
||||
self.update_boundary()
|
||||
debug.info(3, "placing instance {}".format(self))
|
||||
debug.info(3, "placing instance {}".format(self))
|
||||
|
||||
def get_pin(self,name,index=-1):
|
||||
def get_pin(self, name, index=-1):
|
||||
""" Return an absolute pin that is offset and transformed based on
|
||||
this instance location. Index will return one of several pins."""
|
||||
|
||||
import copy
|
||||
if index == -1:
|
||||
pin = copy.deepcopy(self.mod.get_pin(name))
|
||||
pin.transform(self.offset,self.mirror,self.rotate)
|
||||
pin.transform(self.offset, self.mirror, self.rotate)
|
||||
return pin
|
||||
else:
|
||||
pins = copy.deepcopy(self.mod.get_pin(name))
|
||||
pin.transform(self.offset,self.mirror,self.rotate)
|
||||
pins.transform(self.offset, self.mirror, self.rotate)
|
||||
return pin[index]
|
||||
|
||||
def get_num_pins(self, name):
|
||||
""" Return the number of pins of a given name """
|
||||
return len(self.mod.get_pins(name))
|
||||
|
||||
def get_pins(self,name):
|
||||
def get_pins(self, name):
|
||||
""" Return an absolute pin that is offset and transformed based on
|
||||
this instance location. """
|
||||
|
||||
|
|
@ -251,7 +268,7 @@ class instance(geometry):
|
|||
|
||||
new_pins = []
|
||||
for p in pin:
|
||||
p.transform(self.offset,self.mirror,self.rotate)
|
||||
p.transform(self.offset, self.mirror, self.rotate)
|
||||
new_pins.append(p)
|
||||
return new_pins
|
||||
|
||||
|
|
@ -391,14 +408,16 @@ class instance(geometry):
|
|||
""" override print function output """
|
||||
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
|
||||
|
||||
|
||||
class path(geometry):
|
||||
"""Represents a Path"""
|
||||
|
||||
def __init__(self, layerNumber, coordinates, path_width):
|
||||
def __init__(self, lpp, coordinates, path_width):
|
||||
"""Initializes a path for the specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "path"
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.coordinates = map(lambda x: [x[0], x[1]], coordinates)
|
||||
self.coordinates = vector(self.coordinates).snap_to_grid()
|
||||
self.path_width = path_width
|
||||
|
|
@ -411,32 +430,33 @@ class path(geometry):
|
|||
"""Writes the path to GDS"""
|
||||
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
|
||||
new_layout.addPath(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
coordinates=self.coordinates,
|
||||
width=self.path_width)
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Fail since we don't support paths yet. """
|
||||
assert(0)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "path: layer=" + self.layerNumber + " w=" + self.width
|
||||
return "path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( path: layer=" + self.layerNumber + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
||||
return "( path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
||||
|
||||
|
||||
class label(geometry):
|
||||
"""Represents a text label"""
|
||||
|
||||
def __init__(self, text, layerNumber, offset, zoom=-1):
|
||||
def __init__(self, text, lpp, offset, zoom=-1):
|
||||
"""Initializes a text label for specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "label"
|
||||
self.text = text
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
|
||||
if zoom<0:
|
||||
|
|
@ -446,14 +466,14 @@ class label(geometry):
|
|||
|
||||
self.size = 0
|
||||
|
||||
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||
debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Writes the text label to GDS"""
|
||||
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
|
||||
new_layout.addText(text=self.text,
|
||||
layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
offsetInMicrons=self.offset,
|
||||
magnification=self.zoom,
|
||||
rotate=None)
|
||||
|
|
@ -461,24 +481,25 @@ class label(geometry):
|
|||
def get_blockages(self, layer):
|
||||
""" Returns an empty list since text cannot be blockages. """
|
||||
return []
|
||||
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "label: " + self.text + " layer=" + str(self.layerNumber)
|
||||
return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose)
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||
|
||||
|
||||
|
||||
class rectangle(geometry):
|
||||
"""Represents a rectangular shape"""
|
||||
|
||||
def __init__(self, layerNumber, offset, width, height):
|
||||
def __init__(self, lpp, offset, width, height):
|
||||
"""Initializes a rectangular shape for specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "rect"
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
self.size = vector(width, height).snap_to_grid()
|
||||
self.width = round_to_grid(self.size.x)
|
||||
|
|
@ -487,7 +508,7 @@ class rectangle(geometry):
|
|||
|
||||
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Returns a list of one rectangle if it is on this layer"""
|
||||
if self.layerNumber == layer:
|
||||
|
|
@ -502,7 +523,7 @@ class rectangle(geometry):
|
|||
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
|
||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||
new_layout.addBox(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
offsetInMicrons=self.offset,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
|
|
@ -514,4 +535,4 @@ class rectangle(geometry):
|
|||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " )"
|
||||
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||
|
|
|
|||
|
|
@ -7,15 +7,10 @@
|
|||
#
|
||||
import hierarchy_layout
|
||||
import hierarchy_spice
|
||||
import globals
|
||||
import verify
|
||||
import debug
|
||||
import os
|
||||
from globals import OPTS
|
||||
import graph_util
|
||||
|
||||
total_drc_errors = 0
|
||||
total_lvs_errors = 0
|
||||
import tech
|
||||
|
||||
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
"""
|
||||
|
|
@ -28,126 +23,165 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
||||
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
||||
|
||||
# If we have a separate lvs directory, then all the lvs files
|
||||
# should be in there (all or nothing!)
|
||||
try:
|
||||
lvs_subdir = tech.lvs_lib
|
||||
except AttributeError:
|
||||
lvs_subdir = "lvs_lib"
|
||||
lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
|
||||
|
||||
if os.path.exists(lvs_dir):
|
||||
self.lvs_file = lvs_dir + name + ".sp"
|
||||
else:
|
||||
self.lvs_file = self.sp_file
|
||||
|
||||
self.drc_errors = "skipped"
|
||||
self.lvs_errors = "skipped"
|
||||
|
||||
self.name = name
|
||||
hierarchy_spice.spice.__init__(self, name)
|
||||
hierarchy_layout.layout.__init__(self, name)
|
||||
self.init_graph_params()
|
||||
|
||||
def get_layout_pins(self,inst):
|
||||
def get_layout_pins(self, inst):
|
||||
""" Return a map of pin locations of the instance offset """
|
||||
# find the instance
|
||||
for i in self.insts:
|
||||
if i.name == inst.name:
|
||||
break
|
||||
else:
|
||||
debug.error("Couldn't find instance {0}".format(inst_name),-1)
|
||||
debug.error("Couldn't find instance {0}".format(inst.name), -1)
|
||||
inst_map = inst.mod.pin_map
|
||||
return inst_map
|
||||
|
||||
|
||||
def DRC_LVS(self, final_verification=False, top_level=False):
|
||||
def DRC_LVS(self, final_verification=False, force_check=False):
|
||||
"""Checks both DRC and LVS for a module"""
|
||||
|
||||
# Final verification option does not allow nets to be connected by label.
|
||||
# Unit tests will check themselves.
|
||||
if OPTS.is_unit_test:
|
||||
import verify
|
||||
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
if not OPTS.check_lvsdrc:
|
||||
# Unit tests will check themselves.
|
||||
elif not force_check and OPTS.is_unit_test:
|
||||
return
|
||||
elif not force_check and not OPTS.check_lvsdrc:
|
||||
return
|
||||
# Do not run if disabled in options.
|
||||
if (OPTS.inline_lvsdrc or top_level):
|
||||
elif (OPTS.inline_lvsdrc or force_check or final_verification):
|
||||
|
||||
global total_drc_errors
|
||||
global total_lvs_errors
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
self.sp_write(tempspice)
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.lvs_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
# Final verification option does not allow nets to be connected by label.
|
||||
self.drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
||||
self.lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
|
||||
num_drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
||||
num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
debug.check(num_drc_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_drc_errors))
|
||||
debug.check(num_lvs_errors == 0,"LVS failed for {0} with {1} errors(s)".format(self.name,num_lvs_errors))
|
||||
total_drc_errors += num_drc_errors
|
||||
total_lvs_errors += num_lvs_errors
|
||||
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
# force_check is used to determine decoder height and other things, so we shouldn't fail
|
||||
# if that flag is set
|
||||
if OPTS.inline_lvsdrc and not force_check:
|
||||
debug.check(self.drc_errors == 0,
|
||||
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||
self.drc_errors))
|
||||
debug.check(self.lvs_errors == 0,
|
||||
"LVS failed for {0} with {1} errors(s)".format(self.name,
|
||||
self.lvs_errors))
|
||||
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def DRC(self, final_verification=False):
|
||||
"""Checks DRC for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_drc_errors
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.gds_write(tempgds)
|
||||
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
||||
total_drc_errors += num_errors
|
||||
debug.check(num_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_error))
|
||||
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
||||
debug.check(num_errors == 0,
|
||||
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||
num_errors))
|
||||
|
||||
os.remove(tempgds)
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempgds)
|
||||
|
||||
def LVS(self, final_verification=False):
|
||||
"""Checks LVS for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_lvs_errors
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
self.sp_write(tempspice)
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.lvs_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
total_lvs_errors += num_errors
|
||||
debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors))
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
debug.check(num_errors == 0,
|
||||
"LVS failed for {0} with {1} error(s)".format(self.name,
|
||||
num_errors))
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def init_graph_params(self):
|
||||
"""Initializes parameters relevant to the graph creation"""
|
||||
#Only initializes a set for checking instances which should not be added
|
||||
# Only initializes a set for checking instances which should not be added
|
||||
self.graph_inst_exclude = set()
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Recursively create graph from instances in module."""
|
||||
|
||||
#Translate port names to external nets
|
||||
# Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||
self.pins),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
if subinst in self.graph_inst_exclude:
|
||||
continue
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_name = inst_name + '.X' + subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
|
||||
|
||||
def build_names(self, name_dict, inst_name, port_nets):
|
||||
"""Collects all the nets and the parent inst of that net."""
|
||||
#Translate port names to external nets
|
||||
# Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||
self.pins),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_name = inst_name + '.X' + subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
for si_port, conn in zip(subinst_ports, conns):
|
||||
#Only add for first occurrence
|
||||
# Only add for first occurrence
|
||||
if si_port.lower() not in name_dict:
|
||||
mod_info = {'mod':self, 'int_net':conn}
|
||||
mod_info = {'mod': self, 'int_net': conn}
|
||||
name_dict[si_port.lower()] = mod_info
|
||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||
|
||||
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
||||
"""Given a list of nets, will compare the internal alias of a mod to determine
|
||||
if the nets have a connection to this mod's net (but not inst).
|
||||
"""
|
||||
if exclusion_set == None:
|
||||
if not exclusion_set:
|
||||
exclusion_set = set()
|
||||
try:
|
||||
self.name_dict
|
||||
|
|
@ -161,17 +195,17 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
int_mod = self.name_dict[net]['mod']
|
||||
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
||||
aliases.append(net)
|
||||
return aliases
|
||||
return aliases
|
||||
|
||||
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
||||
"""Checks if the alias_net in input mod is the same as the input net for this mod (self)."""
|
||||
if self in exclusion_set:
|
||||
return False
|
||||
#Check ports of this mod
|
||||
# Check ports of this mod
|
||||
for pin in self.pins:
|
||||
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
||||
return True
|
||||
#Check connections of all other subinsts
|
||||
# Check connections of all other subinsts
|
||||
mod_set = set()
|
||||
for subinst, inst_conns in zip(self.insts, self.conns):
|
||||
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
||||
|
|
@ -181,7 +215,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
||||
return True
|
||||
mod_set.add(subinst.mod)
|
||||
return False
|
||||
return False
|
||||
|
||||
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
||||
"""Utility function for checking single net alias."""
|
||||
|
|
@ -190,8 +224,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
parent_net.lower() == alias_net.lower()
|
||||
|
||||
def get_mod_net(self, parent_net, child_inst, child_conns):
|
||||
"""Given an instance and net, returns the internal net in the mod
|
||||
corresponding to input net."""
|
||||
"""
|
||||
Given an instance and net, returns the internal net in the mod
|
||||
corresponding to input net.
|
||||
"""
|
||||
for conn, pin in zip(child_conns, child_inst.mod.pins):
|
||||
if parent_net.lower() == conn.lower():
|
||||
return pin
|
||||
|
|
@ -205,27 +241,27 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
converted_conns.append(port_dict[conn])
|
||||
else:
|
||||
converted_conns.append("{}.{}".format(inst_name, conn))
|
||||
return converted_conns
|
||||
return converted_conns
|
||||
|
||||
def add_graph_edges(self, graph, port_nets):
|
||||
"""For every input, adds an edge to every output.
|
||||
Only intended to be used for gates and other simple modules."""
|
||||
#The final pin names will depend on the spice hierarchy, so
|
||||
#they are passed as an input.
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
# The final pin names will depend on the spice hierarchy, so
|
||||
# they are passed as an input.
|
||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
input_pins = self.get_inputs()
|
||||
output_pins = self.get_outputs()
|
||||
inout_pins = self.get_inouts()
|
||||
for inp in input_pins+inout_pins:
|
||||
for out in output_pins+inout_pins:
|
||||
if inp != out: #do not add self loops
|
||||
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
||||
for inp in input_pins + inout_pins:
|
||||
for out in output_pins + inout_pins:
|
||||
if inp != out: # do not add self loops
|
||||
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
pins = ",".join(self.pins)
|
||||
insts = [" {}".format(x) for x in self.insts]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
s = "********** design {0} **********".format(self.name)
|
||||
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
|
||||
s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs))
|
||||
|
|
@ -236,8 +272,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
""" override print function output """
|
||||
text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
||||
for i in self.objs:
|
||||
text+=str(i)+",\n"
|
||||
text+=str(i) + ",\n"
|
||||
for i in self.insts:
|
||||
text+=str(i)+",\n"
|
||||
text+=str(i) + ",\n"
|
||||
return text
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -10,11 +10,12 @@ import re
|
|||
import os
|
||||
import math
|
||||
import tech
|
||||
from delay_data import *
|
||||
from wire_spice_model import *
|
||||
from power_data import *
|
||||
from delay_data import delay_data
|
||||
from wire_spice_model import wire_spice_model
|
||||
from power_data import power_data
|
||||
import logical_effort
|
||||
|
||||
|
||||
class spice():
|
||||
"""
|
||||
This provides a set of useful generic types for hierarchy
|
||||
|
|
@ -30,19 +31,21 @@ class spice():
|
|||
|
||||
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||
# Holds subckts/mods for this module
|
||||
self.mods = []
|
||||
self.mods = []
|
||||
# Holds the pins for this module
|
||||
self.pins = []
|
||||
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
|
||||
# for each instance, this is the set of nets/nodes that map to the pins for this instance
|
||||
self.pin_type = {}
|
||||
self.pin_type = {}
|
||||
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
|
||||
# Spice format)
|
||||
self.conns = []
|
||||
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
|
||||
self.no_instances = False
|
||||
# Keep track of any comments to add the the spice
|
||||
try:
|
||||
self.commments
|
||||
except:
|
||||
except AttributeError:
|
||||
self.comments = []
|
||||
|
||||
self.sp_read()
|
||||
|
|
@ -56,7 +59,7 @@ class spice():
|
|||
|
||||
try:
|
||||
self.commments
|
||||
except:
|
||||
except AttributeError:
|
||||
self.comments = []
|
||||
|
||||
self.comments.append(comment)
|
||||
|
|
@ -65,7 +68,9 @@ class spice():
|
|||
""" Adds a pin to the pins list. Default type is INOUT signal. """
|
||||
self.pins.append(name)
|
||||
self.pin_type[name]=pin_type
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(name,
|
||||
pin_type))
|
||||
|
||||
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
||||
""" Adds a pin_list to the pins list """
|
||||
|
|
@ -73,36 +78,43 @@ class spice():
|
|||
# or a list that is the same length as the pin list.
|
||||
if type(pin_type)==str:
|
||||
for pin in pin_list:
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,pin_type))
|
||||
self.add_pin(pin,pin_type)
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(pin,
|
||||
pin_type))
|
||||
self.add_pin(pin, pin_type)
|
||||
|
||||
elif len(pin_type)==len(pin_list):
|
||||
for (pin,ptype) in zip(pin_list, pin_type):
|
||||
debug.check(ptype in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,ptype))
|
||||
self.add_pin(pin,ptype)
|
||||
for (pin, ptype) in zip(pin_list, pin_type):
|
||||
debug.check(ptype in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(pin,
|
||||
ptype))
|
||||
self.add_pin(pin, ptype)
|
||||
else:
|
||||
debug.error("Mismatch in type and pin list lengths.", -1)
|
||||
|
||||
def add_pin_types(self, type_list):
|
||||
"""Add pin types for all the cell's pins.
|
||||
Typically, should only be used for handmade cells."""
|
||||
#This only works if self.pins == bitcell.pin_names
|
||||
"""
|
||||
Add pin types for all the cell's pins.
|
||||
Typically, should only be used for handmade cells.
|
||||
"""
|
||||
# This only works if self.pins == bitcell.pin_names
|
||||
if self.pin_names != self.pins:
|
||||
debug.error("{} spice subcircuit port names do not match pin_names\
|
||||
\n SPICE names={}\
|
||||
\n Module names={}\
|
||||
".format(self.name, self.pin_names, self.pins),1)
|
||||
self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)}
|
||||
".format(self.name, self.pin_names, self.pins), 1)
|
||||
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
|
||||
|
||||
def get_pin_type(self, name):
|
||||
""" Returns the type of the signal pin. """
|
||||
pin_type = self.pin_type[name]
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(name, pin_type))
|
||||
return pin_type
|
||||
|
||||
def get_pin_dir(self, name):
|
||||
""" Returns the direction of the pin. (Supply/ground are INOUT). """
|
||||
if self.pin_type[name] in ["POWER","GROUND"]:
|
||||
if self.pin_type[name] in ["POWER", "GROUND"]:
|
||||
return "INOUT"
|
||||
else:
|
||||
return self.pin_type[name]
|
||||
|
|
@ -125,11 +137,10 @@ class spice():
|
|||
output_list.append(pin)
|
||||
return output_list
|
||||
|
||||
|
||||
def copy_pins(self, other_module, suffix=""):
|
||||
""" This will copy all of the pins from the other module and add an optional suffix."""
|
||||
for pin in other_module.pins:
|
||||
self.add_pin(pin+suffix, other_module.get_pin_type(pin))
|
||||
self.add_pin(pin + suffix, other_module.get_pin_type(pin))
|
||||
|
||||
def get_inouts(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
|
|
@ -144,7 +155,6 @@ class spice():
|
|||
"""Adds a subckt/submodule to the subckt hierarchy"""
|
||||
self.mods.append(mod)
|
||||
|
||||
|
||||
def connect_inst(self, args, check=True):
|
||||
"""Connects the pins of the last instance added
|
||||
It is preferred to use the function with the check to find if
|
||||
|
|
@ -169,21 +179,23 @@ class spice():
|
|||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||
len(self.insts),
|
||||
len(self.conns)))
|
||||
debug.error("Instances: \n"+str(insts_string))
|
||||
debug.error("Instances: \n" + str(insts_string))
|
||||
debug.error("-----")
|
||||
debug.error("Connections: \n"+str(conns_string),1)
|
||||
debug.error("Connections: \n" + str(conns_string), 1)
|
||||
|
||||
def get_conns(self, inst):
|
||||
"""Returns the connections of a given instance."""
|
||||
for i in range(len(self.insts)):
|
||||
if inst is self.insts[i]:
|
||||
return self.conns[i]
|
||||
#If not found, returns None
|
||||
# If not found, returns None
|
||||
return None
|
||||
|
||||
def sp_read(self):
|
||||
"""Reads the sp file (and parse the pins) from the library
|
||||
Otherwise, initialize it to null for dynamic generation"""
|
||||
"""
|
||||
Reads the sp file (and parse the pins) from the library
|
||||
Otherwise, initialize it to null for dynamic generation
|
||||
"""
|
||||
if self.sp_file and os.path.isfile(self.sp_file):
|
||||
debug.info(3, "opening {0}".format(self.sp_file))
|
||||
f = open(self.sp_file)
|
||||
|
|
@ -198,17 +210,37 @@ class spice():
|
|||
# parses line into ports and remove subckt
|
||||
self.pins = subckt_line.split(" ")[2:]
|
||||
else:
|
||||
debug.info(4, "no spfile {0}".format(self.sp_file))
|
||||
self.spice = []
|
||||
|
||||
# We don't define self.lvs and will use self.spice if dynamically created
|
||||
# or they are the same file
|
||||
if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
|
||||
debug.info(3, "opening {0}".format(self.lvs_file))
|
||||
f = open(self.lvs_file)
|
||||
self.lvs = f.readlines()
|
||||
for i in range(len(self.lvs)):
|
||||
self.lvs[i] = self.lvs[i].rstrip(" \n")
|
||||
f.close()
|
||||
|
||||
# pins and subckt should be the same
|
||||
# find the correct subckt line in the file
|
||||
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
|
||||
subckt_line = list(filter(subckt.search, self.lvs))[0]
|
||||
# parses line into ports and remove subckt
|
||||
lvs_pins = subckt_line.split(" ")[2:]
|
||||
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
|
||||
|
||||
def check_net_in_spice(self, net_name):
|
||||
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
|
||||
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
|
||||
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
|
||||
# Remove spaces and lower case then add spaces.
|
||||
# Nets are separated by spaces.
|
||||
net_formatted = ' ' + net_name.lstrip().rstrip().lower() + ' '
|
||||
for line in self.spice:
|
||||
#Lowercase the line and remove any part of the line that is a comment.
|
||||
# Lowercase the line and remove any part of the line that is a comment.
|
||||
line = line.lower().split('*')[0]
|
||||
|
||||
#Skip .subckt or .ENDS lines
|
||||
# Skip .subckt or .ENDS lines
|
||||
if line.find('.') == 0:
|
||||
continue
|
||||
if net_formatted in line:
|
||||
|
|
@ -220,7 +252,7 @@ class spice():
|
|||
nets_match = True
|
||||
for net in nets:
|
||||
nets_match = nets_match and self.check_net_in_spice(net)
|
||||
return nets_match
|
||||
return nets_match
|
||||
|
||||
def contains(self, mod, modlist):
|
||||
for x in modlist:
|
||||
|
|
@ -228,54 +260,64 @@ class spice():
|
|||
return True
|
||||
return False
|
||||
|
||||
def sp_write_file(self, sp, usedMODS):
|
||||
""" Recursive spice subcircuit write;
|
||||
Writes the spice subcircuit from the library or the dynamically generated one"""
|
||||
if not self.spice:
|
||||
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
|
||||
"""
|
||||
Recursive spice subcircuit write;
|
||||
Writes the spice subcircuit from the library or the dynamically generated one
|
||||
"""
|
||||
|
||||
if self.no_instances:
|
||||
return
|
||||
elif not self.spice:
|
||||
# If spice isn't defined, we dynamically generate one.
|
||||
|
||||
# recursively write the modules
|
||||
for i in self.mods:
|
||||
if self.contains(i, usedMODS):
|
||||
continue
|
||||
usedMODS.append(i)
|
||||
i.sp_write_file(sp, usedMODS)
|
||||
i.sp_write_file(sp, usedMODS, lvs_netlist)
|
||||
|
||||
if len(self.insts) == 0:
|
||||
return
|
||||
if self.pins == []:
|
||||
return
|
||||
|
||||
|
||||
# write out the first spice line (the subcircuit)
|
||||
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
|
||||
" ".join(self.pins)))
|
||||
|
||||
for pin in self.pins:
|
||||
sp.write("* {1:6}: {0} \n".format(pin,self.pin_type[pin]))
|
||||
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
|
||||
|
||||
for line in self.comments:
|
||||
sp.write("* {}\n".format(line))
|
||||
|
||||
# every instance must have a set of connections, even if it is empty.
|
||||
if len(self.insts)!=len(self.conns):
|
||||
if len(self.insts) != len(self.conns):
|
||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||
len(self.insts),
|
||||
len(self.conns)))
|
||||
debug.error("Instances: \n"+str(self.insts))
|
||||
debug.error("Instances: \n" + str(self.insts))
|
||||
debug.error("-----")
|
||||
debug.error("Connections: \n"+str(self.conns),1)
|
||||
|
||||
|
||||
debug.error("Connections: \n" + str(self.conns), 1)
|
||||
|
||||
for i in range(len(self.insts)):
|
||||
# we don't need to output connections of empty instances.
|
||||
# these are wires and paths
|
||||
if self.conns[i] == []:
|
||||
continue
|
||||
if hasattr(self.insts[i].mod,"spice_device"):
|
||||
# Instance with no devices in it needs no subckt/instance
|
||||
if self.insts[i].mod.no_instances:
|
||||
continue
|
||||
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
|
||||
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
|
||||
" ".join(self.conns[i])))
|
||||
sp.write("\n")
|
||||
elif hasattr(self.insts[i].mod, "spice_device"):
|
||||
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
|
||||
" ".join(self.conns[i])))
|
||||
sp.write("\n")
|
||||
|
||||
else:
|
||||
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
|
||||
" ".join(self.conns[i]),
|
||||
|
|
@ -284,11 +326,14 @@ class spice():
|
|||
sp.write(".ENDS {0}\n".format(self.name))
|
||||
|
||||
else:
|
||||
# write the subcircuit itself
|
||||
# If spice is a hard module, output the spice file contents.
|
||||
# Including the file path makes the unit test fail for other users.
|
||||
#if os.path.isfile(self.sp_file):
|
||||
# if os.path.isfile(self.sp_file):
|
||||
# sp.write("\n* {0}\n".format(self.sp_file))
|
||||
sp.write("\n".join(self.spice))
|
||||
if lvs_netlist and hasattr(self, "lvs"):
|
||||
sp.write("\n".join(self.lvs))
|
||||
else:
|
||||
sp.write("\n".join(self.spice))
|
||||
|
||||
sp.write("\n")
|
||||
|
||||
|
|
@ -302,21 +347,32 @@ class spice():
|
|||
del usedMODS
|
||||
spfile.close()
|
||||
|
||||
def lvs_write(self, spname):
|
||||
"""Writes the lvs to files"""
|
||||
debug.info(3, "Writing to {0}".format(spname))
|
||||
spfile = open(spname, 'w')
|
||||
spfile.write("*FIRST LINE IS A COMMENT\n")
|
||||
usedMODS = list()
|
||||
self.sp_write_file(spfile, usedMODS, True)
|
||||
del usedMODS
|
||||
spfile.close()
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0.0):
|
||||
"""Inform users undefined delay module while building new modules"""
|
||||
|
||||
# FIXME: Slew is not used in the model right now. Can be added heuristically as linear factor
|
||||
# FIXME: Slew is not used in the model right now.
|
||||
# Can be added heuristically as linear factor
|
||||
relative_cap = logical_effort.convert_farad_to_relative_c(load)
|
||||
stage_effort = self.get_stage_effort(relative_cap)
|
||||
|
||||
# If it fails, then keep running with a valid object.
|
||||
if stage_effort == None:
|
||||
if not stage_effort:
|
||||
return delay_data(0.0, 0.0)
|
||||
|
||||
abs_delay = stage_effort.get_absolute_delay()
|
||||
corner_delay = self.apply_corners_analytically(abs_delay, corner)
|
||||
SLEW_APPROXIMATION = 0.1
|
||||
corner_slew = SLEW_APPROXIMATION*corner_delay
|
||||
corner_slew = SLEW_APPROXIMATION * corner_delay
|
||||
return delay_data(corner_delay, corner_slew)
|
||||
|
||||
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||
|
|
@ -326,7 +382,7 @@ class spice():
|
|||
debug.warning("Class {0} name {1}"
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_cin(self):
|
||||
"""Returns input load in Femto-Farads. All values generated using
|
||||
|
|
@ -342,35 +398,35 @@ class spice():
|
|||
debug.warning("Design Class {0} input capacitance function needs to be defined"
|
||||
.format(self.__class__.__name__))
|
||||
debug.warning("Class {0} name {1}"
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return 0
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return 0
|
||||
|
||||
def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5):
|
||||
"""
|
||||
Calculate the delay of a mosfet by
|
||||
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
|
||||
"""
|
||||
Calculate the delay of a mosfet by
|
||||
modeling it as a resistance driving a capacitance
|
||||
"""
|
||||
swing_factor = abs(math.log(1-swing)) # time constant based on swing
|
||||
delay = swing_factor * r * c #c is in ff and delay is in fs
|
||||
swing_factor = abs(math.log(1 - swing)) # time constant based on swing
|
||||
delay = swing_factor * r * c # c is in ff and delay is in fs
|
||||
delay = self.apply_corners_analytically(delay, corner)
|
||||
delay = delay * 0.001 #make the unit to ps
|
||||
delay = delay * 0.001 # make the unit to ps
|
||||
|
||||
# Output slew should be linear to input slew which is described
|
||||
# Output slew should be linear to input slew which is described
|
||||
# as 0.005* slew.
|
||||
|
||||
# The slew will be also influenced by the delay.
|
||||
# If no input slew(or too small to make impact)
|
||||
# The mimum slew should be the time to charge RC.
|
||||
# If no input slew(or too small to make impact)
|
||||
# The mimum slew should be the time to charge RC.
|
||||
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
|
||||
slew = delay * 0.6 * 2 + 0.005 * slew
|
||||
return delay_data(delay = delay, slew = slew)
|
||||
return delay_data(delay=delay, slew=slew)
|
||||
|
||||
def apply_corners_analytically(self, delay, corner):
|
||||
"""Multiply delay by corner factors"""
|
||||
proc,vdd,temp = corner
|
||||
#FIXME: type of delay is needed to know which process to use.
|
||||
proc_mult = max(self.get_process_delay_factor(proc))
|
||||
proc, vdd, temp = corner
|
||||
# FIXME: type of delay is needed to know which process to use.
|
||||
proc_mult = max(self.get_process_delay_factor(proc))
|
||||
volt_mult = self.get_voltage_delay_factor(vdd)
|
||||
temp_mult = self.get_temp_delay_factor(temp)
|
||||
return delay * proc_mult * volt_mult * temp_mult
|
||||
|
|
@ -385,48 +441,51 @@ class spice():
|
|||
elif mos_proc == 'F':
|
||||
proc_factors.append(0.9)
|
||||
elif mos_proc == 'S':
|
||||
proc_factors.append(1.1)
|
||||
proc_factors.append(1.1)
|
||||
return proc_factors
|
||||
|
||||
def get_voltage_delay_factor(self, voltage):
|
||||
"""Returns delay increase due to voltage.
|
||||
Implemented as linear factor based off nominal voltage.
|
||||
"""
|
||||
return tech.spice["nom_supply_voltage"]/voltage
|
||||
return tech.spice["nom_supply_voltage"] / voltage
|
||||
|
||||
def get_temp_delay_factor(self, temp):
|
||||
"""Returns delay increase due to temperature (in C).
|
||||
Determines effect on threshold voltage and then linear factor is estimated.
|
||||
"""
|
||||
#Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
||||
#(k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
||||
thermal_voltage_nom = 0.008625*tech.spice["nom_temperature"]
|
||||
thermal_voltage = 0.008625*temp
|
||||
vthresh = (tech.spice["nom_threshold"]+2*(thermal_voltage-thermal_voltage_nom))
|
||||
#Calculate effect on Vdd-Vth. The current vdd is not used here. A separate vdd factor is calculated.
|
||||
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"])/(tech.spice["nom_supply_voltage"]-vthresh)
|
||||
# Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
||||
# (k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
||||
thermal_voltage_nom = 0.008625 * tech.spice["nom_temperature"]
|
||||
thermal_voltage = 0.008625 * temp
|
||||
vthresh = (tech.spice["nom_threshold"] + 2 * (thermal_voltage - thermal_voltage_nom))
|
||||
# Calculate effect on Vdd-Vth.
|
||||
# The current vdd is not used here.
|
||||
# A separate vdd factor is calculated.
|
||||
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"]) / (tech.spice["nom_supply_voltage"] - vthresh)
|
||||
|
||||
def return_delay(self, delay, slew):
|
||||
return delay_data(delay, slew)
|
||||
|
||||
def generate_rc_net(self,lump_num, wire_length, wire_width):
|
||||
def generate_rc_net(self, lump_num, wire_length, wire_width):
|
||||
return wire_spice_model(lump_num, wire_length, wire_width)
|
||||
|
||||
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
|
||||
"""
|
||||
"""
|
||||
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
|
||||
"""
|
||||
proc,vdd,temp = corner
|
||||
net_vswing = vdd*swing
|
||||
power_dyn = c*vdd*net_vswing*freq
|
||||
proc, vdd, temp = corner
|
||||
net_vswing = vdd * swing
|
||||
power_dyn = c * vdd * net_vswing * freq
|
||||
|
||||
#Apply process and temperature factors. Roughly, process and Vdd affect the delay which affects the power.
|
||||
#No other estimations are currently used. Increased delay->slower freq.->less power
|
||||
proc_div = max(self.get_process_delay_factor(proc))
|
||||
# A pply process and temperature factors.
|
||||
# Roughly, process and Vdd affect the delay which affects the power.
|
||||
# No other estimations are currently used. Increased delay->slower freq.->less power
|
||||
proc_div = max(self.get_process_delay_factor(proc))
|
||||
temp_div = self.get_temp_delay_factor(temp)
|
||||
power_dyn = power_dyn/(proc_div*temp_div)
|
||||
power_dyn = power_dyn / (proc_div * temp_div)
|
||||
|
||||
return power_dyn
|
||||
return power_dyn
|
||||
|
||||
def return_power(self, dynamic=0.0, leakage=0.0):
|
||||
return power_data(dynamic, leakage)
|
||||
|
|
|
|||
|
|
@ -8,72 +8,117 @@
|
|||
import debug
|
||||
from tech import GDS, drc
|
||||
from vector import vector
|
||||
from tech import layer
|
||||
from tech import layer, layer_indices
|
||||
import math
|
||||
|
||||
|
||||
class pin_layout:
|
||||
"""
|
||||
A class to represent a rectangular design pin. It is limited to a
|
||||
single shape.
|
||||
"""
|
||||
|
||||
def __init__(self, name, rect, layer_name_num):
|
||||
def __init__(self, name, rect, layer_name_pp):
|
||||
self.name = name
|
||||
# repack the rect as a vector, just in case
|
||||
if type(rect[0])==vector:
|
||||
self.rect = rect
|
||||
if type(rect[0]) == vector:
|
||||
self._rect = rect
|
||||
else:
|
||||
self.rect = [vector(rect[0]),vector(rect[1])]
|
||||
self._rect = [vector(rect[0]), vector(rect[1])]
|
||||
# snap the rect to the grid
|
||||
self.rect = [x.snap_to_grid() for x in self.rect]
|
||||
self._rect = [x.snap_to_grid() for x in self.rect]
|
||||
|
||||
debug.check(self.width()>0,"Zero width pin.")
|
||||
debug.check(self.height()>0,"Zero height pin.")
|
||||
debug.check(self.width() > 0, "Zero width pin.")
|
||||
debug.check(self.height() > 0, "Zero height pin.")
|
||||
|
||||
# These are the valid pin layers
|
||||
valid_layers = { x: layer[x] for x in layer_indices.keys()}
|
||||
|
||||
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
||||
if type(layer_name_num)==int:
|
||||
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
||||
# if it's a string, use the name
|
||||
if type(layer_name_pp) == str:
|
||||
self._layer = layer_name_pp
|
||||
# else it is required to be a lpp
|
||||
else:
|
||||
self.layer=layer_name_num
|
||||
self.layer_num = layer[self.layer]
|
||||
for (layer_name, lpp) in valid_layers.items():
|
||||
if not lpp:
|
||||
continue
|
||||
if self.same_lpp(layer_name_pp, lpp):
|
||||
self._layer = layer_name
|
||||
break
|
||||
else:
|
||||
debug.error("Couldn't find layer {}".format(layer_name_pp), -1)
|
||||
|
||||
self.lpp = layer[self.layer]
|
||||
self._recompute_hash()
|
||||
|
||||
@property
|
||||
def layer(self):
|
||||
return self._layer
|
||||
|
||||
@layer.setter
|
||||
def layer(self, l):
|
||||
self._layer = l
|
||||
self._recompute_hash()
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
return self._rect
|
||||
|
||||
@rect.setter
|
||||
def rect(self, r):
|
||||
self._rect = r
|
||||
self._recompute_hash()
|
||||
|
||||
def _recompute_hash(self):
|
||||
""" Recompute the hash for our hash cache """
|
||||
self._hash = hash(repr(self))
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
||||
return "({} layer={} ll={} ur={})".format(self.name,
|
||||
self.layer,
|
||||
self.rect[0],
|
||||
self.rect[1])
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
override repr function output (don't include
|
||||
"""
|
||||
override repr function output (don't include
|
||||
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
||||
"""
|
||||
return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1])
|
||||
return "(layer={} ll={} ur={})".format(self.layer,
|
||||
self.rect[0],
|
||||
self.rect[1])
|
||||
|
||||
def __hash__(self):
|
||||
""" Implement the hash function for sets etc. """
|
||||
return hash(repr(self))
|
||||
|
||||
"""
|
||||
Implement the hash function for sets etc. We only return a cached
|
||||
value, that is updated when either 'rect' or 'layer' are changed. This
|
||||
is a major speedup, if pin_layout is used as a key for dicts.
|
||||
"""
|
||||
return self._hash
|
||||
|
||||
def __lt__(self, other):
|
||||
""" Provide a function for ordering items by the ll point """
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
|
||||
if ll.x < oll.x and ll.y < oll.y:
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Check if these are the same pins for duplicate checks """
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.layer==other.layer and self.rect == other.rect)
|
||||
return (self.lpp == other.lpp and self.rect == other.rect)
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def bbox(self, pin_list):
|
||||
"""
|
||||
Given a list of layout pins, create a bounding box layout.
|
||||
"""
|
||||
(ll, ur) = self.rect
|
||||
(ll, ur) = self.rect
|
||||
min_x = ll.x
|
||||
max_x = ur.x
|
||||
min_y = ll.y
|
||||
|
|
@ -85,39 +130,46 @@ class pin_layout:
|
|||
min_y = min(min_y, pin.ll().y)
|
||||
max_y = max(max_y, pin.ur().y)
|
||||
|
||||
self.rect = [vector(min_x,min_y),vector(max_x,max_y)]
|
||||
|
||||
self.rect = [vector(min_x, min_y), vector(max_x, max_y)]
|
||||
|
||||
def fix_minarea(self):
|
||||
"""
|
||||
Try to fix minimum area rule.
|
||||
"""
|
||||
min_area = drc("{}_minarea".format(self.layer))
|
||||
pass
|
||||
|
||||
def inflate(self, spacing=None):
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
if not spacing:
|
||||
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||
|
||||
(ll,ur) = self.rect
|
||||
|
||||
(ll, ur) = self.rect
|
||||
spacing = vector(spacing, spacing)
|
||||
newll = ll - spacing
|
||||
newur = ur + spacing
|
||||
|
||||
|
||||
return (newll, newur)
|
||||
|
||||
def intersection(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
min_x = max(ll.x, oll.x)
|
||||
max_x = min(ll.x, oll.x)
|
||||
min_y = max(ll.y, oll.y)
|
||||
max_y = min(ll.y, oll.y)
|
||||
|
||||
return [vector(min_x,min_y),vector(max_x,max_y)]
|
||||
return [vector(min_x, min_y), vector(max_x, max_y)]
|
||||
|
||||
def xoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
x_overlaps = False
|
||||
# check if self is within other x range
|
||||
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
|
||||
|
|
@ -130,8 +182,8 @@ class pin_layout:
|
|||
|
||||
def yoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
y_overlaps = False
|
||||
|
||||
# check if self is within other y range
|
||||
|
|
@ -142,29 +194,29 @@ class pin_layout:
|
|||
y_overlaps = True
|
||||
|
||||
return y_overlaps
|
||||
|
||||
|
||||
def xcontains(self, other):
|
||||
""" Check if shape contains the x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
return (oll.x >= ll.x and our.x <= ur.x)
|
||||
|
||||
def ycontains(self, other):
|
||||
""" Check if shape contains the y overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
return (oll.y >= ll.y and our.y <= ur.y)
|
||||
|
||||
|
||||
def contains(self, other):
|
||||
""" Check if a shape contains another rectangle """
|
||||
# If it is the same shape entirely, it is contained!
|
||||
if self == other:
|
||||
return True
|
||||
|
||||
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
if not self.same_lpp(self.lpp, other.lpp):
|
||||
return False
|
||||
|
||||
if not self.xcontains(other):
|
||||
|
|
@ -181,14 +233,13 @@ class pin_layout:
|
|||
if shape.contains(self):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def overlaps(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
if not self.same_lpp(self.lpp, other.lpp):
|
||||
return False
|
||||
|
||||
|
||||
x_overlaps = self.xoverlaps(other)
|
||||
y_overlaps = self.yoverlaps(other)
|
||||
|
||||
|
|
@ -197,51 +248,56 @@ class pin_layout:
|
|||
def area(self):
|
||||
""" Return the area. """
|
||||
return self.height()*self.width()
|
||||
|
||||
|
||||
def height(self):
|
||||
""" Return height. Abs is for pre-normalized value."""
|
||||
return abs(self.rect[1].y-self.rect[0].y)
|
||||
|
||||
|
||||
def width(self):
|
||||
""" Return width. Abs is for pre-normalized value."""
|
||||
return abs(self.rect[1].x-self.rect[0].x)
|
||||
|
||||
def normalize(self):
|
||||
""" Re-find the LL and UR points after a transform """
|
||||
(first,second)=self.rect
|
||||
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
|
||||
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
|
||||
self.rect=[ll,ur]
|
||||
|
||||
def transform(self,offset,mirror,rotate):
|
||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance. """
|
||||
(ll,ur) = self.rect
|
||||
if mirror=="MX":
|
||||
ll=ll.scale(1,-1)
|
||||
ur=ur.scale(1,-1)
|
||||
elif mirror=="MY":
|
||||
ll=ll.scale(-1,1)
|
||||
ur=ur.scale(-1,1)
|
||||
elif mirror=="XY":
|
||||
ll=ll.scale(-1,-1)
|
||||
ur=ur.scale(-1,-1)
|
||||
|
||||
if rotate==90:
|
||||
ll=ll.rotate_scale(-1,1)
|
||||
ur=ur.rotate_scale(-1,1)
|
||||
elif rotate==180:
|
||||
ll=ll.scale(-1,-1)
|
||||
ur=ur.scale(-1,-1)
|
||||
elif rotate==270:
|
||||
ll=ll.rotate_scale(1,-1)
|
||||
ur=ur.rotate_scale(1,-1)
|
||||
(first, second) = self.rect
|
||||
ll = vector(min(first[0], second[0]), min(first[1], second[1]))
|
||||
ur = vector(max(first[0], second[0]), max(first[1], second[1]))
|
||||
self.rect=[ll, ur]
|
||||
|
||||
self.rect=[offset+ll,offset+ur]
|
||||
def transform(self, offset, mirror, rotate):
|
||||
"""
|
||||
Transform with offset, mirror and rotation
|
||||
to get the absolute pin location.
|
||||
We must then re-find the ll and ur.
|
||||
The master is the cell instance.
|
||||
"""
|
||||
(ll, ur) = self.rect
|
||||
if mirror == "MX":
|
||||
ll = ll.scale(1, -1)
|
||||
ur = ur.scale(1, -1)
|
||||
elif mirror == "MY":
|
||||
ll = ll.scale(-1, 1)
|
||||
ur = ur.scale(-1, 1)
|
||||
elif mirror == "XY":
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
|
||||
if rotate == 90:
|
||||
ll = ll.rotate_scale(-1, 1)
|
||||
ur = ur.rotate_scale(-1, 1)
|
||||
elif rotate == 180:
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
elif rotate == 270:
|
||||
ll = ll.rotate_scale(1, -1)
|
||||
ur = ur.rotate_scale(1, -1)
|
||||
|
||||
self.rect = [offset + ll, offset + ur]
|
||||
self.normalize()
|
||||
|
||||
def center(self):
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),0.5*(self.rect[0].y+self.rect[1].y))
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def cx(self):
|
||||
""" Center x """
|
||||
|
|
@ -250,7 +306,7 @@ class pin_layout:
|
|||
def cy(self):
|
||||
""" Center y """
|
||||
return 0.5*(self.rect[0].y+self.rect[1].y)
|
||||
|
||||
|
||||
# The four possible corners
|
||||
def ll(self):
|
||||
""" Lower left point """
|
||||
|
|
@ -258,17 +314,17 @@ class pin_layout:
|
|||
|
||||
def ul(self):
|
||||
""" Upper left point """
|
||||
return vector(self.rect[0].x,self.rect[1].y)
|
||||
return vector(self.rect[0].x, self.rect[1].y)
|
||||
|
||||
def lr(self):
|
||||
""" Lower right point """
|
||||
return vector(self.rect[1].x,self.rect[0].y)
|
||||
return vector(self.rect[1].x, self.rect[0].y)
|
||||
|
||||
def ur(self):
|
||||
""" Upper right point """
|
||||
return self.rect[1]
|
||||
|
||||
# The possible y edge values
|
||||
|
||||
# The possible y edge values
|
||||
def uy(self):
|
||||
""" Upper y value """
|
||||
return self.rect[1].y
|
||||
|
|
@ -278,80 +334,93 @@ class pin_layout:
|
|||
return self.rect[0].y
|
||||
|
||||
# The possible x edge values
|
||||
|
||||
|
||||
def lx(self):
|
||||
""" Left x value """
|
||||
return self.rect[0].x
|
||||
|
||||
|
||||
def rx(self):
|
||||
""" Right x value """
|
||||
return self.rect[1].x
|
||||
|
||||
|
||||
|
||||
# The edge centers
|
||||
def rc(self):
|
||||
""" Right center point """
|
||||
return vector(self.rect[1].x,0.5*(self.rect[0].y+self.rect[1].y))
|
||||
return vector(self.rect[1].x,
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def lc(self):
|
||||
""" Left center point """
|
||||
return vector(self.rect[0].x,0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
return vector(self.rect[0].x,
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def uc(self):
|
||||
""" Upper center point """
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[1].y)
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
self.rect[1].y)
|
||||
|
||||
def bc(self):
|
||||
""" Bottom center point """
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[0].y)
|
||||
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
self.rect[0].y)
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
"""Writes the pin shape and label to GDS"""
|
||||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||
+ str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll()))
|
||||
newLayout.addBox(layerNumber=layer[self.layer],
|
||||
purposeNumber=0,
|
||||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||
+ str(self.width()) + "x"
|
||||
+ str(self.height()) + " @ " + str(self.ll()))
|
||||
(layer_num, purpose) = layer[self.layer]
|
||||
try:
|
||||
from tech import pin_purpose
|
||||
except ImportError:
|
||||
pin_purpose = purpose
|
||||
try:
|
||||
from tech import label_purpose
|
||||
except ImportError:
|
||||
label_purpose = purpose
|
||||
|
||||
newLayout.addBox(layerNumber=layer_num,
|
||||
purposeNumber=pin_purpose,
|
||||
offsetInMicrons=self.ll(),
|
||||
width=self.width(),
|
||||
height=self.height(),
|
||||
center=False)
|
||||
# Add the tet in the middle of the pin.
|
||||
# This fixes some pin label offsetting when GDS gets imported into Magic.
|
||||
# This fixes some pin label offsetting when GDS gets
|
||||
# imported into Magic.
|
||||
newLayout.addText(text=self.name,
|
||||
layerNumber=layer[self.layer],
|
||||
purposeNumber=0,
|
||||
layerNumber=layer_num,
|
||||
purposeNumber=label_purpose,
|
||||
offsetInMicrons=self.center(),
|
||||
magnification=GDS["zoom"],
|
||||
rotate=None)
|
||||
|
||||
|
||||
def compute_overlap(self, other):
|
||||
""" Calculate the rectangular overlap of two rectangles. """
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
#ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
# ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
# ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
|
||||
dy = min(r1_ur.y,r2_ur.y)-max(r1_ll.y,r2_ll.y)
|
||||
dx = min(r1_ur.x,r2_ur.x)-max(r1_ll.x,r2_ll.x)
|
||||
|
||||
if dx>=0 and dy>=0:
|
||||
return [dx,dy]
|
||||
dy = min(r1_ur.y, r2_ur.y) - max(r1_ll.y, r2_ll.y)
|
||||
dx = min(r1_ur.x, r2_ur.x) - max(r1_ll.x, r2_ll.x)
|
||||
|
||||
if dx >= 0 and dy >= 0:
|
||||
return [dx, dy]
|
||||
else:
|
||||
return [0,0]
|
||||
return [0, 0]
|
||||
|
||||
def distance(self, other):
|
||||
"""
|
||||
"""
|
||||
Calculate the distance to another pin layout.
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
def dist(x1, y1, x2, y2):
|
||||
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
||||
|
||||
|
||||
left = r2_ur.x < r1_ll.x
|
||||
right = r1_ur.x < r2_ll.x
|
||||
bottom = r2_ur.y < r1_ll.y
|
||||
|
|
@ -368,7 +437,7 @@ class pin_layout:
|
|||
elif left:
|
||||
return r1_ll.x - r2_ur.x
|
||||
elif right:
|
||||
return r2_ll.x - r1.ur.x
|
||||
return r2_ll.x - r1_ur.x
|
||||
elif bottom:
|
||||
return r1_ll.y - r2_ur.y
|
||||
elif top:
|
||||
|
|
@ -376,10 +445,9 @@ class pin_layout:
|
|||
else:
|
||||
# rectangles intersect
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
def overlap_length(self, other):
|
||||
"""
|
||||
"""
|
||||
Calculate the intersection segment and determine its length
|
||||
"""
|
||||
|
||||
|
|
@ -391,21 +459,21 @@ class pin_layout:
|
|||
intersections = self.compute_overlap_segment(other)
|
||||
# This is the common case where two pairs of edges overlap
|
||||
# at two points, so just find the distance between those two points
|
||||
if len(intersections)==2:
|
||||
(p1,p2) = intersections
|
||||
return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2))
|
||||
if len(intersections) == 2:
|
||||
(p1, p2) = intersections
|
||||
return math.sqrt(pow(p1[0]-p2[0], 2) + pow(p1[1]-p2[1], 2))
|
||||
else:
|
||||
# This is where we had a corner intersection or none
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
def compute_overlap_segment(self, other):
|
||||
"""
|
||||
Calculate the intersection segment of two rectangles
|
||||
"""
|
||||
Calculate the intersection segment of two rectangles
|
||||
(if any)
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
# The other corners besides ll and ur
|
||||
r1_ul = vector(r1_ll.x, r1_ur.y)
|
||||
|
|
@ -414,23 +482,24 @@ class pin_layout:
|
|||
r2_lr = vector(r2_ur.x, r2_ll.y)
|
||||
|
||||
from itertools import tee
|
||||
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
# R1 edges CW
|
||||
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
||||
r1_edges = []
|
||||
for (p,q) in pairwise(r1_cw_points):
|
||||
r1_edges.append([p,q])
|
||||
|
||||
for (p, q) in pairwise(r1_cw_points):
|
||||
r1_edges.append([p, q])
|
||||
|
||||
# R2 edges CW
|
||||
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
||||
r2_edges = []
|
||||
for (p,q) in pairwise(r2_cw_points):
|
||||
r2_edges.append([p,q])
|
||||
for (p, q) in pairwise(r2_cw_points):
|
||||
r2_edges.append([p, q])
|
||||
|
||||
# There are 4 edges on each rectangle
|
||||
# so just brute force check intersection of each
|
||||
|
|
@ -452,36 +521,46 @@ class pin_layout:
|
|||
q.x >= min(p.x, r.x) and \
|
||||
q.y <= max(p.y, r.y) and \
|
||||
q.y >= min(p.y, r.y):
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def segment_intersection(self, s1, s2):
|
||||
"""
|
||||
"""
|
||||
Determine the intersection point of two segments
|
||||
Return the a segment if they overlap.
|
||||
Return None if they don't.
|
||||
"""
|
||||
(a,b) = s1
|
||||
(c,d) = s2
|
||||
(a, b) = s1
|
||||
(c, d) = s2
|
||||
# Line AB represented as a1x + b1y = c1
|
||||
a1 = b.y - a.y
|
||||
b1 = a.x - b.x
|
||||
c1 = a1*a.x + b1*a.y
|
||||
|
||||
|
||||
# Line CD represented as a2x + b2y = c2
|
||||
a2 = d.y - c.y
|
||||
b2 = c.x - d.x
|
||||
c2 = a2*c.x + b2*c.y
|
||||
|
||||
|
||||
determinant = a1*b2 - a2*b1
|
||||
|
||||
if determinant!=0:
|
||||
if determinant != 0:
|
||||
x = (b2*c1 - b1*c2)/determinant
|
||||
y = (a1*c2 - a2*c1)/determinant
|
||||
|
||||
r = vector(x,y).snap_to_grid()
|
||||
|
||||
r = vector(x, y).snap_to_grid()
|
||||
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
||||
return r
|
||||
|
||||
|
||||
return None
|
||||
|
||||
def same_lpp(self, lpp1, lpp2):
|
||||
"""
|
||||
Check if the layers and purposes are the same.
|
||||
Ignore if purpose is a None.
|
||||
"""
|
||||
if lpp1[1] == None or lpp2[1] == None:
|
||||
return lpp1[0] == lpp2[0]
|
||||
|
||||
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import os
|
||||
import gdsMill
|
||||
import tech
|
||||
import math
|
||||
|
|
@ -16,6 +15,7 @@ from pin_layout import pin_layout
|
|||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
|
||||
def ceil(decimal):
|
||||
"""
|
||||
Performs a ceiling function on the decimal place specified by the DRC grid.
|
||||
|
|
@ -23,29 +23,35 @@ def ceil(decimal):
|
|||
grid = tech.drc["grid"]
|
||||
return math.ceil(decimal * 1 / grid) / (1 / grid)
|
||||
|
||||
|
||||
def round_to_grid(number):
|
||||
"""
|
||||
Rounds an arbitrary number to the grid.
|
||||
"""
|
||||
grid = tech.drc["grid"]
|
||||
grid = tech.drc["grid"]
|
||||
# this gets the nearest integer value
|
||||
number_grid = int(round(round((number / grid), 2), 0))
|
||||
number_off = number_grid * grid
|
||||
return number_off
|
||||
|
||||
|
||||
def snap_to_grid(offset):
|
||||
"""
|
||||
Changes the coodrinate to match the grid settings
|
||||
"""
|
||||
return [round_to_grid(offset[0]),round_to_grid(offset[1])]
|
||||
return [round_to_grid(offset[0]),
|
||||
round_to_grid(offset[1])]
|
||||
|
||||
|
||||
def pin_center(boundary):
|
||||
"""
|
||||
This returns the center of a pin shape in the vlsiLayout border format.
|
||||
"""
|
||||
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
|
||||
return [0.5 * (boundary[0] + boundary[2]),
|
||||
0.5 * (boundary[1] + boundary[3])]
|
||||
|
||||
def auto_measure_libcell(pin_list, name, units, layer):
|
||||
|
||||
def auto_measure_libcell(pin_list, name, units, lpp):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Return these as a set of properties including the cell width/height too.
|
||||
|
|
@ -56,43 +62,44 @@ def auto_measure_libcell(pin_list, name, units, layer):
|
|||
reader.loadFromFile(cell_gds)
|
||||
|
||||
cell = {}
|
||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||
if measure_result == None:
|
||||
measure_result = cell_vlsi.getLayoutBorder(lpp[0])
|
||||
if measure_result:
|
||||
measure_result = cell_vlsi.measureSize(name)
|
||||
[cell["width"], cell["height"]] = measure_result
|
||||
|
||||
for pin in pin_list:
|
||||
(name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin))
|
||||
(name, lpp, boundary) = cell_vlsi.getPinShapeByLabel(str(pin))
|
||||
cell[str(pin)] = pin_center(boundary)
|
||||
return cell
|
||||
|
||||
|
||||
|
||||
def get_gds_size(name, gds_filename, units, layer):
|
||||
def get_gds_size(name, gds_filename, units, lpp):
|
||||
"""
|
||||
Open a GDS file and return the size from either the
|
||||
bounding box or a border layer.
|
||||
"""
|
||||
debug.info(4,"Creating VLSI layout for {}".format(name))
|
||||
debug.info(4, "Creating VLSI layout for {}".format(name))
|
||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||
reader.loadFromFile(gds_filename)
|
||||
|
||||
cell = {}
|
||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||
if measure_result == None:
|
||||
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
|
||||
measure_result = cell_vlsi.getLayoutBorder(lpp)
|
||||
if not measure_result:
|
||||
debug.info(2, "Layout border failed. Trying to measure size for {}".format(name))
|
||||
measure_result = cell_vlsi.measureSize(name)
|
||||
# returns width,height
|
||||
return measure_result
|
||||
|
||||
def get_libcell_size(name, units, layer):
|
||||
|
||||
def get_libcell_size(name, units, lpp):
|
||||
"""
|
||||
Open a GDS file and return the library cell size from either the
|
||||
bounding box or a border layer.
|
||||
"""
|
||||
|
||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||
return(get_gds_size(name, cell_gds, units, layer))
|
||||
return(get_gds_size(name, cell_gds, units, lpp))
|
||||
|
||||
|
||||
|
||||
def get_gds_pins(pin_names, name, gds_filename, units):
|
||||
|
|
@ -106,20 +113,24 @@ def get_gds_pins(pin_names, name, gds_filename, units):
|
|||
|
||||
cell = {}
|
||||
for pin_name in pin_names:
|
||||
cell[str(pin_name)]=[]
|
||||
pin_list=cell_vlsi.getPinShape(str(pin_name))
|
||||
cell[str(pin_name)] = []
|
||||
pin_list = cell_vlsi.getPinShape(str(pin_name))
|
||||
for pin_shape in pin_list:
|
||||
(layer,boundary)=pin_shape
|
||||
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
||||
# this is a list because other cells/designs may have must-connect pins
|
||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
|
||||
(lpp, boundary) = pin_shape
|
||||
rect = [vector(boundary[0], boundary[1]),
|
||||
vector(boundary[2], boundary[3])]
|
||||
# this is a list because other cells/designs
|
||||
# may have must-connect pins
|
||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp))
|
||||
return cell
|
||||
|
||||
|
||||
def get_libcell_pins(pin_list, name, units):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Return these as a rectangle layer pair for each pin.
|
||||
"""
|
||||
|
||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||
return(get_gds_pins(pin_list, name, cell_gds, units))
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class vector():
|
|||
else:
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
self._hash = hash((self.x,self.y))
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
|
|
@ -49,7 +50,7 @@ class vector():
|
|||
else:
|
||||
self.x=float(value[0])
|
||||
self.y=float(value[1])
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
override getitem function
|
||||
|
|
@ -97,7 +98,7 @@ class vector():
|
|||
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
||||
break things.
|
||||
"""
|
||||
return hash((self.x,self.y))
|
||||
return self._hash
|
||||
|
||||
def snap_to_grid(self):
|
||||
self.x = self.snap_offset_to_grid(self.x)
|
||||
|
|
|
|||
|
|
@ -6,23 +6,27 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import contact
|
||||
from wire_path import wire_path
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class wire(wire_path):
|
||||
"""
|
||||
"""
|
||||
Object metal wire; given the layer type
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
The points should be rectilinear to control the bend points. If
|
||||
not, it will always go down first.
|
||||
The points are the center of the wire.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
The widen option will avoid via-to-via spacing problems for really short segments
|
||||
(added as an option so we can disable it in bus connections)
|
||||
"""
|
||||
def __init__(self, obj, layer_stack, position_list):
|
||||
def __init__(self, obj, layer_stack, position_list, widen_short_wires=True):
|
||||
self.obj = obj
|
||||
self.layer_stack = layer_stack
|
||||
self.position_list = position_list
|
||||
self.widen_short_wires = widen_short_wires
|
||||
self.pins = [] # used for matching parm lengths
|
||||
self.switch_pos_list = []
|
||||
|
||||
|
|
@ -36,6 +40,7 @@ class wire(wire_path):
|
|||
# wires and wire_paths should not be offset to (0,0)
|
||||
|
||||
def setup_layers(self):
|
||||
|
||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
||||
self.via_layer_name = via_layer
|
||||
|
||||
|
|
@ -47,21 +52,49 @@ class wire(wire_path):
|
|||
via_connect = factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
|
||||
# This is used for short connections to avoid via-to-via spacing errors
|
||||
self.vert_layer_contact_width = max(via_connect.second_layer_width,
|
||||
via_connect.first_layer_width)
|
||||
self.horiz_layer_contact_width = max(via_connect.second_layer_height,
|
||||
via_connect.first_layer_height)
|
||||
|
||||
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
||||
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
|
||||
self.pitch = self.compute_pitch(self.layer_stack)
|
||||
|
||||
def compute_pitch(self, layer_stack):
|
||||
|
||||
"""
|
||||
This is contact direction independent pitch,
|
||||
i.e. we take the maximum contact dimension
|
||||
"""
|
||||
(layer1, via, layer2) = layer_stack
|
||||
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
contact1 = getattr(contact, layer1 + "_contact")
|
||||
else:
|
||||
try:
|
||||
contact1 = getattr(contact, layer1 + "_via")
|
||||
except AttributeError:
|
||||
contact1 = getattr(contact, layer2 + "_via")
|
||||
max_contact = max(contact1.width, contact1.height)
|
||||
|
||||
layer1_space = drc("{0}_to_{0}".format(layer1))
|
||||
layer2_space = drc("{0}_to_{0}".format(layer2))
|
||||
pitch = max_contact + max(layer1_space, layer2_space)
|
||||
|
||||
return pitch
|
||||
|
||||
# create a 1x1 contact
|
||||
def create_vias(self):
|
||||
""" Add a via and corner square at every corner of the path."""
|
||||
self.c=factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
c_width = self.c.width
|
||||
c_height = self.c.height
|
||||
|
||||
from itertools import tee,islice
|
||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
||||
threewise=nwise(self.position_list,3)
|
||||
from itertools import tee, islice
|
||||
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||
threewise = nwise(self.position_list, 3)
|
||||
|
||||
for (a, offset, c) in list(threewise):
|
||||
# add a exceptions to prevent a via when we don't change directions
|
||||
|
|
@ -72,18 +105,25 @@ class wire(wire_path):
|
|||
self.obj.add_via_center(layers=self.layer_stack,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def create_rectangles(self):
|
||||
"""
|
||||
"""
|
||||
Create the actual rectangles on the appropriate layers
|
||||
using the position list of the corners.
|
||||
using the position list of the corners.
|
||||
"""
|
||||
pl = self.position_list # position list
|
||||
for index in range(len(pl) - 1):
|
||||
# Horizontal wire segment
|
||||
if pl[index][0] != pl[index + 1][0]:
|
||||
line_length = pl[index + 1][0] - pl[index][0]
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
|
||||
width = self.horiz_layer_contact_width
|
||||
else:
|
||||
width = self.horiz_layer_width
|
||||
temp_offset = [pl[index][0],
|
||||
pl[index][1] - 0.5*self.horiz_layer_width]
|
||||
pl[index][1] - 0.5 * width]
|
||||
# If we go in the negative direction, move the offset
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0] + line_length,
|
||||
temp_offset[1]]
|
||||
|
|
@ -91,10 +131,17 @@ class wire(wire_path):
|
|||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="horizontal",
|
||||
layer_width=self.horiz_layer_width)
|
||||
layer_width=width)
|
||||
# Vertical wire segment
|
||||
elif pl[index][1] != pl[index + 1][1]:
|
||||
line_length = pl[index + 1][1] - pl[index][1]
|
||||
temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width,
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
|
||||
width = self.vert_layer_contact_width
|
||||
else:
|
||||
width = self.vert_layer_width
|
||||
temp_offset = [pl[index][0] - 0.5 * width,
|
||||
pl[index][1]]
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0],
|
||||
|
|
@ -103,11 +150,13 @@ class wire(wire_path):
|
|||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="vertical",
|
||||
layer_width=self.vert_layer_width)
|
||||
layer_width=width)
|
||||
|
||||
def assert_node(self, A, B):
|
||||
""" Check if the node movements are not big enough for the
|
||||
technology sizes."""
|
||||
"""
|
||||
Check if the node movements are not big enough for the
|
||||
technology sizes.
|
||||
"""
|
||||
X_diff = abs(A[0] - B[0])
|
||||
Y_diff = abs(A[1] - B[1])
|
||||
[minX, minY] = self.node_to_node
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ def create_rectilinear_route(my_list):
|
|||
my_list.append(vector(pl[index][0], pl[index + 1][1]))
|
||||
my_list.append(vector(pl[-1]))
|
||||
return my_list
|
||||
|
||||
|
||||
|
||||
class wire_path():
|
||||
"""
|
||||
Object metal wire_path; given the layer type
|
||||
Add a wire_path of minimium metal width between a set of points.
|
||||
Add a wire_path of minimium metal width between a set of points.
|
||||
The points should be rectilinear to control the bend points. If
|
||||
not, it will always go down first. The points are the center of the wire_path.
|
||||
If width is not given, it uses minimum layer width.
|
||||
|
|
@ -37,7 +38,7 @@ class wire_path():
|
|||
self.obj = obj
|
||||
self.layer_name = layer
|
||||
self.layer_id = techlayer[layer]
|
||||
if width==None:
|
||||
if width == None:
|
||||
self.layer_width = drc["minwidth_{0}".format(layer)]
|
||||
else:
|
||||
self.layer_width = width
|
||||
|
|
@ -46,7 +47,6 @@ class wire_path():
|
|||
self.switch_pos_list = []
|
||||
self.create_layout()
|
||||
|
||||
|
||||
def create_layout(self):
|
||||
self.create_rectilinear()
|
||||
self.connect_corner()
|
||||
|
|
@ -60,9 +60,9 @@ class wire_path():
|
|||
|
||||
def connect_corner(self):
|
||||
""" Add a corner square at every corner of the wire_path."""
|
||||
from itertools import tee,islice
|
||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
||||
threewise=nwise(self.position_list,3)
|
||||
from itertools import tee, islice
|
||||
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||
threewise=nwise(self.position_list, 3)
|
||||
|
||||
for (a, offset, c) in list(threewise):
|
||||
# add a exceptions to prevent a corner when we retrace back in the same direction
|
||||
|
|
@ -74,7 +74,6 @@ class wire_path():
|
|||
offset[1] - 0.5 * self.layer_width]
|
||||
self.draw_corner_wire(corner_offset)
|
||||
|
||||
|
||||
def draw_corner_wire(self, offset):
|
||||
""" This function adds the corner squares since the center
|
||||
line convention only draws to the center of the corner."""
|
||||
|
|
@ -117,7 +116,7 @@ class wire_path():
|
|||
|
||||
def add_line(self, layer_name, length, offset, orientation, layer_width):
|
||||
"""
|
||||
straight line object with layer_minwidth
|
||||
straight line object with layer_minwidth
|
||||
(orientation: "vertical" or "horizontal") default is vertical
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS, layer
|
||||
from tech import cell_properties as props
|
||||
import bitcell_base
|
||||
|
||||
from globals import OPTS
|
||||
|
||||
class bitcell(bitcell_base.bitcell_base):
|
||||
"""
|
||||
|
|
@ -18,10 +19,22 @@ class bitcell(bitcell_base.bitcell_base):
|
|||
the layout and netlist should be available in the technology
|
||||
library.
|
||||
"""
|
||||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
storage_nets = ['Q', 'Q_bar']
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
|
||||
# If we have a split WL bitcell, if not be backwards
|
||||
# compatible in the tech file
|
||||
|
||||
if props.bitcell.split_wl:
|
||||
pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
else:
|
||||
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||
props.bitcell.cell_6t.pin.br,
|
||||
props.bitcell.cell_6t.pin.wl,
|
||||
props.bitcell.cell_6t.pin.vdd,
|
||||
props.bitcell.cell_6t.pin.gnd]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
storage_nets = ['Q', 'Qbar']
|
||||
|
||||
(width, height) = utils.get_libcell_size("cell_6t",
|
||||
GDS["unit"],
|
||||
layer["boundary"])
|
||||
|
|
@ -37,42 +50,49 @@ class bitcell(bitcell_base.bitcell_base):
|
|||
self.pin_map = bitcell.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
|
||||
debug.check(OPTS.tech_name != "sky130", "sky130 does not yet support single port cells")
|
||||
|
||||
def get_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
row_pins = ["wl"]
|
||||
if props.bitcell.split_wl:
|
||||
row_pins = ["wl0", "wl1"]
|
||||
else:
|
||||
row_pins = [props.bitcell.cell_6t.pin.wl]
|
||||
return row_pins
|
||||
|
||||
|
||||
def get_all_bitline_names(self):
|
||||
""" Creates a list of all bitline pin names (both bl and br) """
|
||||
column_pins = ["bl", "br"]
|
||||
pin = props.bitcell.cell_6t.pin
|
||||
column_pins = [pin.bl, pin.br]
|
||||
return column_pins
|
||||
|
||||
|
||||
def get_all_bl_names(self):
|
||||
""" Creates a list of all bl pins names """
|
||||
column_pins = ["bl"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_6t.pin.bl]
|
||||
|
||||
def get_all_br_names(self):
|
||||
""" Creates a list of all br pins names """
|
||||
column_pins = ["br"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_6t.pin.br]
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name"""
|
||||
debug.check(port == 0, "One port for bitcell only.")
|
||||
return "bl"
|
||||
|
||||
return props.bitcell.cell_6t.pin.bl
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name"""
|
||||
debug.check(port == 0, "One port for bitcell only.")
|
||||
return "br"
|
||||
return props.bitcell.cell_6t.pin.br
|
||||
|
||||
def get_wl_name(self, port=0):
|
||||
"""Get wl name"""
|
||||
debug.check(port == 0, "One port for bitcell only.")
|
||||
return "wl"
|
||||
|
||||
if props.bitcell.split_wl:
|
||||
return "wl{}".format(port)
|
||||
else:
|
||||
debug.check(port == 0, "One port for bitcell only.")
|
||||
return props.bitcell.cell_6t.pin.wl
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""
|
||||
Adds edges based on inputs/outputs.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS, layer, parameter, drc
|
||||
from tech import cell_properties as props
|
||||
import logical_effort
|
||||
import bitcell_base
|
||||
|
||||
|
|
@ -20,7 +21,15 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base):
|
|||
library.
|
||||
"""
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1,
|
||||
props.bitcell.cell_1rw1r.pin.br1,
|
||||
props.bitcell.cell_1rw1r.pin.wl0,
|
||||
props.bitcell.cell_1rw1r.pin.wl1,
|
||||
props.bitcell.cell_1rw1r.pin.vdd,
|
||||
props.bitcell.cell_1rw1r.pin.gnd]
|
||||
|
||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||
storage_nets = ['Q', 'Q_bar']
|
||||
|
|
@ -39,85 +48,92 @@ class bitcell_1rw_1r(bitcell_base.bitcell_base):
|
|||
self.pin_map = bitcell_1rw_1r.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
|
||||
pin_names = bitcell_1rw_1r.pin_names
|
||||
self.bl_names = [pin_names[0], pin_names[2]]
|
||||
self.br_names = [pin_names[1], pin_names[3]]
|
||||
self.wl_names = [pin_names[4], pin_names[5]]
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
"""
|
||||
Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array
|
||||
"""
|
||||
bitcell_pins = ["bl0_{0}".format(col),
|
||||
"br0_{0}".format(col),
|
||||
"bl1_{0}".format(col),
|
||||
"br1_{0}".format(col),
|
||||
"wl0_{0}".format(row),
|
||||
"wl1_{0}".format(row),
|
||||
pin_name = props.bitcell.cell_1rw1r.pin
|
||||
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
|
||||
"{0}_{1}".format(pin_name.br0, col),
|
||||
"{0}_{1}".format(pin_name.bl1, col),
|
||||
"{0}_{1}".format(pin_name.br1, col),
|
||||
"{0}_{1}".format(pin_name.wl0, row),
|
||||
"{0}_{1}".format(pin_name.wl1, row),
|
||||
"vdd",
|
||||
"gnd"]
|
||||
return bitcell_pins
|
||||
|
||||
|
||||
def get_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
row_pins = ["wl0", "wl1"]
|
||||
return row_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.wl0,
|
||||
props.bitcell.cell_1rw1r.pin.wl1]
|
||||
|
||||
def get_all_bitline_names(self):
|
||||
""" Creates a list of all bitline pin names (both bl and br) """
|
||||
column_pins = ["bl0", "br0", "bl1", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1,
|
||||
props.bitcell.cell_1rw1r.pin.br1]
|
||||
|
||||
def get_all_bl_names(self):
|
||||
""" Creates a list of all bl pins names """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1]
|
||||
|
||||
def get_all_br_names(self):
|
||||
""" Creates a list of all br pins names """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.br1]
|
||||
|
||||
def get_read_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with read ports """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1]
|
||||
|
||||
def get_read_br_names(self):
|
||||
""" Creates a list of br pin names associated with read ports """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.br1]
|
||||
|
||||
def get_write_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with write ports """
|
||||
column_pins = ["bl0"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.bl0]
|
||||
|
||||
def get_write_br_names(self):
|
||||
""" Creates a list of br pin names asscociated with write ports"""
|
||||
column_pins = ["br0"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1rw1r.pin.br1]
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||
return "bl{}".format(port)
|
||||
|
||||
return self.bl_names[port]
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||
return "br{}".format(port)
|
||||
return self.br_names[port]
|
||||
|
||||
def get_wl_name(self, port=0):
|
||||
"""Get wl name by port"""
|
||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||
return "wl{}".format(port)
|
||||
|
||||
return self.wl_names[port]
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
|
||||
pins = props.bitcell.cell_1rw1r.pin
|
||||
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self)
|
||||
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self)
|
||||
# Port 1 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS, layer
|
||||
from tech import cell_properties as props
|
||||
import bitcell_base
|
||||
|
||||
|
||||
|
|
@ -19,7 +20,14 @@ class bitcell_1w_1r(bitcell_base.bitcell_base):
|
|||
library.
|
||||
"""
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.bl1,
|
||||
props.bitcell.cell_1w1r.pin.br1,
|
||||
props.bitcell.cell_1w1r.pin.wl0,
|
||||
props.bitcell.cell_1w1r.pin.wl1,
|
||||
props.bitcell.cell_1w1r.pin.vdd,
|
||||
props.bitcell.cell_1w1r.pin.gnd]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||
storage_nets = ['Q', 'Q_bar']
|
||||
|
|
@ -39,80 +47,88 @@ class bitcell_1w_1r(bitcell_base.bitcell_base):
|
|||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
pin_names = bitcell_1w_1r.pin_names
|
||||
self.bl_names = [pin_names[0], pin_names[2]]
|
||||
self.br_names = [pin_names[1], pin_names[3]]
|
||||
self.wl_names = [pin_names[4], pin_names[5]]
|
||||
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
"""
|
||||
Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array
|
||||
"""
|
||||
bitcell_pins = ["bl0_{0}".format(col),
|
||||
"br0_{0}".format(col),
|
||||
"bl1_{0}".format(col),
|
||||
"br1_{0}".format(col),
|
||||
"wl0_{0}".format(row),
|
||||
"wl1_{0}".format(row),
|
||||
pin_name = props.bitcell.cell_1w1r.pin
|
||||
bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col),
|
||||
"{0}_{1}".format(pin_name.br0, col),
|
||||
"{0}_{1}".format(pin_name.bl1, col),
|
||||
"{0}_{1}".format(pin_name.br1, col),
|
||||
"{0}_{1}".format(pin_name.wl0, row),
|
||||
"{0}_{1}".format(pin_name.wl1, row),
|
||||
"vdd",
|
||||
"gnd"]
|
||||
return bitcell_pins
|
||||
|
||||
|
||||
def get_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
row_pins = ["wl0", "wl1"]
|
||||
return row_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.wl0,
|
||||
props.bitcell.cell_1w1r.pin.wl1]
|
||||
|
||||
def get_all_bitline_names(self):
|
||||
""" Creates a list of all bitline pin names (both bl and br) """
|
||||
column_pins = ["bl0", "br0", "bl1", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.bl1,
|
||||
props.bitcell.cell_1w1r.pin.br1]
|
||||
|
||||
def get_all_bl_names(self):
|
||||
""" Creates a list of all bl pins names """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.bl1]
|
||||
|
||||
def get_all_br_names(self):
|
||||
""" Creates a list of all br pins names """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.br1]
|
||||
|
||||
def get_read_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with read ports """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.bl1]
|
||||
|
||||
def get_read_br_names(self):
|
||||
""" Creates a list of br pin names associated with read ports """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.br1]
|
||||
|
||||
def get_write_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with write ports """
|
||||
column_pins = ["bl0"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.bl0]
|
||||
|
||||
def get_write_br_names(self):
|
||||
""" Creates a list of br pin names asscociated with write ports"""
|
||||
column_pins = ["br0"]
|
||||
return column_pins
|
||||
|
||||
return [props.bitcell.cell_1w1r.pin.br1]
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "bl{}".format(port)
|
||||
|
||||
return self.bl_names[port]
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "br{}".format(port)
|
||||
|
||||
return self.br_names[port]
|
||||
|
||||
def get_wl_name(self, port=0):
|
||||
"""Get wl name by port"""
|
||||
debug.check(port < 2, "Two ports for bitcell_1rw_1r only.")
|
||||
return "wl{}".format(port)
|
||||
|
||||
return self.wl_names[port]
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
pins = props.bitcell.cell_1w1r.pin
|
||||
# Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||
# Port 1 is a write port, so its timing is not considered here.
|
||||
|
|
|
|||
|
|
@ -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 utils
|
||||
from tech import GDS, layer
|
||||
from tech import cell_properties as props
|
||||
import bitcell_base
|
||||
|
||||
|
||||
|
|
@ -18,8 +19,12 @@ class dummy_bitcell(bitcell_base.bitcell_base):
|
|||
the layout and netlist should be available in the technology
|
||||
library.
|
||||
"""
|
||||
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||
props.bitcell.cell_6t.pin.br,
|
||||
props.bitcell.cell_6t.pin.wl,
|
||||
props.bitcell.cell_6t.pin.vdd,
|
||||
props.bitcell.cell_6t.pin.gnd]
|
||||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
(width, height) = utils.get_libcell_size("dummy_cell_6t",
|
||||
GDS["unit"],
|
||||
layer["boundary"])
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS, layer
|
||||
from tech import cell_properties as props
|
||||
import bitcell_base
|
||||
|
||||
|
||||
|
|
@ -18,7 +19,15 @@ class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base):
|
|||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1,
|
||||
props.bitcell.cell_1rw1r.pin.br1,
|
||||
props.bitcell.cell_1rw1r.pin.wl0,
|
||||
props.bitcell.cell_1rw1r.pin.wl1,
|
||||
props.bitcell.cell_1rw1r.pin.vdd,
|
||||
props.bitcell.cell_1rw1r.pin.gnd]
|
||||
|
||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT",
|
||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width, height) = utils.get_libcell_size("dummy_cell_1rw_1r",
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS, layer
|
||||
from tech import cell_properties as props
|
||||
import bitcell_base
|
||||
|
||||
|
||||
|
|
@ -18,7 +19,14 @@ class dummy_bitcell_1w_1r(bitcell_base.bitcell_base):
|
|||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.bl1,
|
||||
props.bitcell.cell_1w1r.pin.br1,
|
||||
props.bitcell.cell_1w1r.pin.wl0,
|
||||
props.bitcell.cell_1w1r.pin.wl1,
|
||||
props.bitcell.cell_1w1r.pin.vdd,
|
||||
props.bitcell.cell_1w1r.pin.gnd]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT",
|
||||
"INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width, height) = utils.get_libcell_size("dummy_cell_1w_1r",
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class dummy_pbitcell(design.design):
|
|||
|
||||
self.create_netlist()
|
||||
self.create_layout()
|
||||
self.add_boundary()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#
|
||||
import contact
|
||||
import debug
|
||||
from tech import drc, parameter
|
||||
from tech import drc, parameter, layer
|
||||
from vector import vector
|
||||
from ptx import ptx
|
||||
from globals import OPTS
|
||||
|
|
@ -199,7 +199,6 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
def calculate_spacing(self):
|
||||
""" Calculate transistor spacings """
|
||||
|
||||
# calculate metal contact extensions over transistor active
|
||||
readwrite_nmos_contact_extension = 0.5 * \
|
||||
(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
||||
|
|
@ -213,20 +212,20 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# y-offset for the access transistor's gate contact
|
||||
self.gate_contact_yoffset = max_contact_extension + self.m2_space \
|
||||
+ 0.5 * max(contact.poly.height, contact.m1m2.height)
|
||||
+ 0.5 * max(contact.poly_contact.height, contact.m1_via.height)
|
||||
|
||||
# y-position of access transistors
|
||||
self.port_ypos = self.m1_space + 0.5 * contact.m1m2.height + self.gate_contact_yoffset
|
||||
self.port_ypos = self.m1_space + 0.5 * contact.m1_via.height + self.gate_contact_yoffset
|
||||
|
||||
# y-position of inverter nmos
|
||||
self.inverter_nmos_ypos = self.port_ypos
|
||||
|
||||
# spacing between ports (same for read/write and write ports)
|
||||
self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \
|
||||
+ 0.5 * contact.m1m2.height \
|
||||
+ 0.5 * contact.m1_via.height \
|
||||
+ self.m2_space + self.m2_width
|
||||
m2_constraint = self.bitline_offset + self.m2_space \
|
||||
+ 0.5 * contact.m1m2.height \
|
||||
+ 0.5 * contact.m1_via.height \
|
||||
- 0.5 * self.readwrite_nmos.active_width
|
||||
self.write_port_spacing = max(self.active_space,
|
||||
self.m1_space,
|
||||
|
|
@ -234,7 +233,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
self.read_port_spacing = self.bitline_offset + self.m2_space
|
||||
|
||||
# spacing between cross coupled inverters
|
||||
self.inverter_to_inverter_spacing = contact.poly.width + self.m1_space
|
||||
self.inverter_to_inverter_spacing = contact.poly_contact.width + self.m1_space
|
||||
|
||||
# calculations related to inverter connections
|
||||
inverter_pmos_contact_extension = 0.5 * \
|
||||
|
|
@ -243,19 +242,19 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
|
||||
self.inverter_gap = max(self.poly_to_active,
|
||||
self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ self.poly_to_polycontact + 2 * contact.poly.width \
|
||||
+ self.poly_to_contact + 2 * contact.poly_contact.width \
|
||||
+ self.m1_space + inverter_pmos_contact_extension
|
||||
self.cross_couple_lower_ypos = self.inverter_nmos_ypos \
|
||||
+ self.inverter_nmos.active_height \
|
||||
+ max(self.poly_to_active,
|
||||
self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ 0.5 * contact.poly.width
|
||||
+ 0.5 * contact.poly_contact.width
|
||||
self.cross_couple_upper_ypos = self.inverter_nmos_ypos \
|
||||
+ self.inverter_nmos.active_height \
|
||||
+ max(self.poly_to_active,
|
||||
self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ self.poly_to_polycontact \
|
||||
+ 1.5 * contact.poly.width
|
||||
+ self.poly_to_contact \
|
||||
+ 1.5 * contact.poly_contact.width
|
||||
|
||||
# spacing between wordlines (and gnd)
|
||||
self.m1_offset = -0.5 * self.m1_width
|
||||
|
|
@ -263,9 +262,9 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
# spacing for vdd
|
||||
implant_constraint = max(inverter_pmos_contact_extension, 0) \
|
||||
+ 2 * self.implant_enclose_active \
|
||||
+ 0.5 * (contact.well.width - self.m1_width)
|
||||
+ 0.5*(self.inverter_pmos.active_contact.height - self.m1_width)
|
||||
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
|
||||
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
|
||||
self.vdd_offset = max(implant_constraint, metal1_constraint) + self.m1_width
|
||||
|
||||
# read port dimensions
|
||||
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
|
||||
|
|
@ -276,7 +275,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
Calculate positions that describe the edges
|
||||
and dimensions of the cell
|
||||
"""
|
||||
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch
|
||||
self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
|
||||
self.topmost_ypos = self.inverter_nmos_ypos \
|
||||
+ self.inverter_nmos.active_height \
|
||||
+ self.inverter_gap \
|
||||
|
|
@ -291,7 +290,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
(self.write_nmos.active_width + self.write_port_spacing) \
|
||||
- self.num_r_ports * \
|
||||
(self.read_port_width + self.read_port_spacing) \
|
||||
- self.bitline_offset - 0.5 * contact.m1m2.width
|
||||
- self.bitline_offset - 0.5 * contact.m1_via.width
|
||||
|
||||
self.width = -2 * self.leftmost_xpos
|
||||
self.height = self.topmost_ypos - self.botmost_ypos
|
||||
|
|
@ -369,29 +368,28 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
self.inverter_pmos_right.get_pin("G").bc()])
|
||||
|
||||
# connect output (drain/source) of inverters
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.inverter_nmos_left.get_pin("D").uc(),
|
||||
self.inverter_pmos_left.get_pin("D").bc()],
|
||||
width=contact.active.second_layer_width)
|
||||
self.add_path("metal1",
|
||||
width=self.inverter_nmos_left.get_pin("D").width())
|
||||
self.add_path("m1",
|
||||
[self.inverter_nmos_right.get_pin("S").uc(),
|
||||
self.inverter_pmos_right.get_pin("S").bc()],
|
||||
width=contact.active.second_layer_width)
|
||||
width=self.inverter_nmos_right.get_pin("S").width())
|
||||
|
||||
# add contacts to connect gate poly to drain/source
|
||||
# metal1 (to connect Q to Q_bar)
|
||||
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \
|
||||
+ 0.5 * contact.poly.height,
|
||||
+ 0.5 * contact.poly_contact.height,
|
||||
self.cross_couple_upper_ypos)
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=contact_offset_left,
|
||||
directions=("H", "H"))
|
||||
|
||||
|
||||
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \
|
||||
- 0.5*contact.poly.height,
|
||||
- 0.5*contact.poly_contact.height,
|
||||
self.cross_couple_lower_ypos)
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=contact_offset_right,
|
||||
directions=("H", "H"))
|
||||
|
||||
|
|
@ -417,12 +415,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
def route_rails(self):
|
||||
""" Adds gnd and vdd rails and connects them to the inverters """
|
||||
# Add rails for vdd and gnd
|
||||
gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch
|
||||
gnd_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch
|
||||
self.gnd_position = vector(0, gnd_ypos)
|
||||
self.add_rect_center(layer="metal1",
|
||||
self.add_rect_center(layer="m1",
|
||||
offset=self.gnd_position,
|
||||
width=self.width)
|
||||
self.add_power_pin("gnd", vector(0, gnd_ypos))
|
||||
self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H"))
|
||||
|
||||
|
||||
vdd_ypos = self.inverter_nmos_ypos \
|
||||
|
|
@ -431,10 +429,10 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
+ self.inverter_pmos.active_height \
|
||||
+ self.vdd_offset
|
||||
self.vdd_position = vector(0, vdd_ypos)
|
||||
self.add_rect_center(layer="metal1",
|
||||
self.add_rect_center(layer="m1",
|
||||
offset=self.vdd_position,
|
||||
width=self.width)
|
||||
self.add_power_pin("vdd", vector(0, vdd_ypos))
|
||||
self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H"))
|
||||
|
||||
def create_readwrite_ports(self):
|
||||
"""
|
||||
|
|
@ -501,10 +499,10 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
self.port_ypos])
|
||||
|
||||
# add pin for RWWL
|
||||
rwwl_ypos = self.m1_offset - k * self.m1_pitch
|
||||
rwwl_ypos = self.m1_offset - k * self.m1_nonpref_pitch
|
||||
self.rwwl_positions[k] = vector(0, rwwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=self.rwwl_positions[k],
|
||||
width=self.width)
|
||||
|
||||
|
|
@ -514,7 +512,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
+ 0.5 * self.m2_width
|
||||
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.rwbl_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -524,7 +522,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
- 0.5 * self.m2_width
|
||||
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.rwbr_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -597,11 +595,11 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# add pin for WWL
|
||||
wwl_ypos = rwwl_ypos = self.m1_offset \
|
||||
- self.num_rw_ports * self.m1_pitch \
|
||||
- k * self.m1_pitch
|
||||
- self.num_rw_ports * self.m1_nonpref_pitch \
|
||||
- k * self.m1_nonpref_pitch
|
||||
self.wwl_positions[k] = vector(0, wwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=self.wwl_positions[k],
|
||||
width=self.width)
|
||||
|
||||
|
|
@ -611,7 +609,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
+ 0.5 * self.m2_width
|
||||
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.wbl_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -621,7 +619,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
- 0.5 * self.m2_width
|
||||
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_br_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.wbr_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -723,12 +721,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# add pin for RWL
|
||||
rwl_ypos = rwwl_ypos = self.m1_offset \
|
||||
- self.num_rw_ports * self.m1_pitch \
|
||||
- self.num_w_ports * self.m1_pitch \
|
||||
- k * self.m1_pitch
|
||||
- self.num_rw_ports * self.m1_nonpref_pitch \
|
||||
- self.num_w_ports * self.m1_nonpref_pitch \
|
||||
- k * self.m1_nonpref_pitch
|
||||
self.rwl_positions[k] = vector(0, rwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=self.rwl_positions[k],
|
||||
width=self.width)
|
||||
|
||||
|
|
@ -738,7 +736,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
+ 0.5 * self.m2_width
|
||||
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.rbl_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -748,7 +746,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
- 0.5 * self.m2_width
|
||||
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_br_names[k],
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=self.rbr_positions[k],
|
||||
height=self.height)
|
||||
|
||||
|
|
@ -786,25 +784,25 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
# first transistor on either side of the cross coupled inverters
|
||||
# does not need to route to wordline on metal2
|
||||
if (k == 0) or (k == 1):
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=port_contact_offset)
|
||||
|
||||
|
||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[port_contact_offset, wl_contact_offset])
|
||||
|
||||
else:
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=port_contact_offset)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=wl_contact_offset,
|
||||
directions=("H", "H"))
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=wl_contact_offset)
|
||||
|
||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||
self.add_path("metal2",
|
||||
self.add_path("m2",
|
||||
[port_contact_offset, wl_contact_offset])
|
||||
|
||||
def route_bitlines(self):
|
||||
|
|
@ -839,11 +837,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# Leave bitline disconnected if a dummy cell
|
||||
if not self.dummy_bitcell:
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=port_contact_offest)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=port_contact_offest,
|
||||
directions="nonpref")
|
||||
|
||||
self.add_path("metal2",
|
||||
[port_contact_offest, bl_offset], width=contact.m1m2.height)
|
||||
self.add_path("m2",
|
||||
[port_contact_offest, bl_offset], width=contact.m1_via.height)
|
||||
|
||||
for k in range(self.total_ports):
|
||||
port_contact_offest = right_port_transistors[k].get_pin("D").center()
|
||||
|
|
@ -851,11 +850,12 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# Leave bitline disconnected if a dummy cell
|
||||
if not self.dummy_bitcell:
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=port_contact_offest)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=port_contact_offest,
|
||||
directions="nonpref")
|
||||
|
||||
self.add_path("metal2",
|
||||
[port_contact_offest, br_offset], width=contact.m1m2.height)
|
||||
self.add_path("m2",
|
||||
[port_contact_offest, br_offset], width=contact.m1_via.height)
|
||||
|
||||
def route_supply(self):
|
||||
""" Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """
|
||||
|
|
@ -868,30 +868,32 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
|
||||
|
||||
for position in nmos_contact_positions:
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=position)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=position,
|
||||
directions=("V", "V"))
|
||||
|
||||
|
||||
if position.x > 0:
|
||||
contact_correct = 0.5 * contact.m1m2.height
|
||||
contact_correct = 0.5 * contact.m1_via.height
|
||||
else:
|
||||
contact_correct = -0.5 * contact.m1m2.height
|
||||
contact_correct = -0.5 * contact.m1_via.height
|
||||
supply_offset = vector(position.x + contact_correct,
|
||||
self.gnd_position.y)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=supply_offset,
|
||||
directions=("H", "H"))
|
||||
|
||||
self.add_path("metal2", [position, supply_offset])
|
||||
self.add_path("m2", [position, supply_offset])
|
||||
|
||||
# route inverter pmos to vdd
|
||||
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x,
|
||||
self.vdd_position.y)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
||||
|
||||
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x,
|
||||
self.vdd_position.y)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
||||
|
||||
def route_readwrite_access(self):
|
||||
|
|
@ -904,14 +906,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
self.cross_couple_lower_ypos)
|
||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
||||
self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
||||
|
||||
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x,
|
||||
self.cross_couple_lower_ypos)
|
||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
||||
self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
||||
|
||||
def route_write_access(self):
|
||||
|
|
@ -924,14 +926,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
self.cross_couple_lower_ypos)
|
||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(),
|
||||
self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos])
|
||||
|
||||
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x,
|
||||
self.cross_couple_lower_ypos)
|
||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(),
|
||||
self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos])
|
||||
|
||||
def route_read_access(self):
|
||||
|
|
@ -941,16 +943,17 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
"""
|
||||
# add poly to metal1 contacts for gates of the inverters
|
||||
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \
|
||||
- self.poly_to_polycontact - 0.5*contact.poly.width,
|
||||
- self.poly_to_contact - 0.5*contact.poly_contact.width,
|
||||
self.cross_couple_upper_ypos)
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=left_storage_contact,
|
||||
directions=("H", "H"))
|
||||
|
||||
|
||||
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \
|
||||
+ self.poly_to_polycontact + 0.5*contact.poly.width,
|
||||
+ self.poly_to_contact + 0.5*contact.poly_contact.width,
|
||||
self.cross_couple_upper_ypos)
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=right_storage_contact,
|
||||
directions=("H", "H"))
|
||||
|
||||
|
|
@ -967,7 +970,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
+ vector(0,
|
||||
self.gate_contact_yoffset - self.poly_extend_active)
|
||||
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_path("poly",
|
||||
|
|
@ -975,14 +978,14 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x,
|
||||
self.cross_couple_upper_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[port_contact_offset, mid, left_storage_contact])
|
||||
|
||||
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \
|
||||
+ vector(0,
|
||||
self.gate_contact_yoffset - self.poly_extend_active)
|
||||
|
||||
self.add_via_center(layers=("poly", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_path("poly",
|
||||
|
|
@ -990,51 +993,55 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x,
|
||||
self.cross_couple_upper_ypos)
|
||||
self.add_path("metal1",
|
||||
self.add_path("m1",
|
||||
[port_contact_offset, mid, right_storage_contact])
|
||||
|
||||
def extend_well(self):
|
||||
"""
|
||||
Connects wells between ptx modules and places well contacts
|
||||
"""
|
||||
# extend pwell to encompass entire nmos region of the cell up to the
|
||||
# height of the tallest nmos transistor
|
||||
max_nmos_well_height = max(self.inverter_nmos.cell_well_height,
|
||||
self.readwrite_nmos.cell_well_height,
|
||||
self.write_nmos.cell_well_height,
|
||||
self.read_nmos.cell_well_height)
|
||||
well_height = max_nmos_well_height + self.port_ypos \
|
||||
- self.well_enclose_active - self.gnd_position.y
|
||||
offset = vector(self.leftmost_xpos, self.botmost_ypos)
|
||||
self.add_rect(layer="pwell",
|
||||
offset=offset,
|
||||
width=self.width,
|
||||
height=well_height)
|
||||
|
||||
if "pwell" in layer:
|
||||
# extend pwell to encompass entire nmos region of the cell up to the
|
||||
# height of the tallest nmos transistor
|
||||
max_nmos_well_height = max(self.inverter_nmos.well_height,
|
||||
self.readwrite_nmos.well_height,
|
||||
self.write_nmos.well_height,
|
||||
self.read_nmos.well_height)
|
||||
well_height = max_nmos_well_height + self.port_ypos \
|
||||
- self.nwell_enclose_active - self.gnd_position.y
|
||||
# FIXME fudge factor xpos
|
||||
well_width = self.width + 2*self.nwell_enclose_active
|
||||
offset = vector(self.leftmost_xpos - self.nwell_enclose_active, self.botmost_ypos)
|
||||
self.add_rect(layer="pwell",
|
||||
offset=offset,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
|
||||
# extend nwell to encompass inverter_pmos
|
||||
# calculate offset of the left pmos well
|
||||
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||
- self.well_enclose_active
|
||||
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||
+ self.inverter_gap - self.well_enclose_active
|
||||
if "nwell" in layer:
|
||||
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||
- self.nwell_enclose_active
|
||||
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||
+ self.inverter_gap - self.nwell_enclose_active
|
||||
|
||||
# calculate width of the two combined nwells
|
||||
# calculate height to encompass nimplant connected to vdd
|
||||
well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||
+ 2 * self.nwell_enclose_active
|
||||
well_height = self.vdd_position.y - inverter_well_ypos \
|
||||
+ self.nwell_enclose_active + drc["minwidth_tx"]
|
||||
|
||||
# calculate width of the two combined nwells
|
||||
# calculate height to encompass nimplant connected to vdd
|
||||
well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \
|
||||
+ 2 * self.well_enclose_active
|
||||
well_height = self.vdd_position.y - inverter_well_ypos \
|
||||
+ self.well_enclose_active + drc["minwidth_tx"]
|
||||
|
||||
offset = [inverter_well_xpos, inverter_well_ypos]
|
||||
self.add_rect(layer="nwell",
|
||||
offset=offset,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
offset = [inverter_well_xpos, inverter_well_ypos]
|
||||
self.add_rect(layer="nwell",
|
||||
offset=offset,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
|
||||
# add well contacts
|
||||
# connect pimplants to gnd
|
||||
offset = vector(0, self.gnd_position.y)
|
||||
self.add_via_center(layers=("active", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.active_stack,
|
||||
offset=offset,
|
||||
directions=("H", "H"),
|
||||
implant_type="p",
|
||||
|
|
@ -1042,7 +1049,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
|
||||
# connect nimplants to vdd
|
||||
offset = vector(0, self.vdd_position.y)
|
||||
self.add_via_center(layers=("active", "contact", "metal1"),
|
||||
self.add_via_center(layers=self.active_stack,
|
||||
offset=offset,
|
||||
directions=("H", "H"),
|
||||
implant_type="n",
|
||||
|
|
@ -1091,7 +1098,7 @@ class pbitcell(bitcell_base.bitcell_base):
|
|||
"""
|
||||
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
|
||||
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
||||
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|
||||
self.add_path("m1", [Q_bar_pos, vdd_pos])
|
||||
|
||||
def get_storage_net_names(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer,drc,parameter
|
||||
from tech import GDS,layer,drc,parameter,cell_properties
|
||||
from tech import cell_properties as props
|
||||
|
||||
from globals import OPTS
|
||||
|
||||
class replica_bitcell(design.design):
|
||||
"""
|
||||
|
|
@ -17,10 +20,24 @@ class replica_bitcell(design.design):
|
|||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||
if cell_properties.bitcell.split_wl:
|
||||
pin_names = ["bl", "br", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT" , "POWER", "GROUND"]
|
||||
else:
|
||||
pin_names = [props.bitcell.cell_6t.pin.bl,
|
||||
props.bitcell.cell_6t.pin.br,
|
||||
props.bitcell.cell_6t.pin.wl,
|
||||
props.bitcell.cell_6t.pin.vdd,
|
||||
props.bitcell.cell_6t.pin.gnd]
|
||||
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
|
||||
if not OPTS.netlist_only:
|
||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||
else:
|
||||
(width,height) = (0,0)
|
||||
pin_map = []
|
||||
|
||||
def __init__(self, name=""):
|
||||
# Ignore the name argument
|
||||
|
|
@ -56,4 +73,4 @@ class replica_bitcell(design.design):
|
|||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import design
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer,drc,parameter
|
||||
from tech import cell_properties as props
|
||||
|
||||
class replica_bitcell_1rw_1r(design.design):
|
||||
"""
|
||||
|
|
@ -17,7 +18,15 @@ class replica_bitcell_1rw_1r(design.design):
|
|||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1rw1r.pin.bl0,
|
||||
props.bitcell.cell_1rw1r.pin.br0,
|
||||
props.bitcell.cell_1rw1r.pin.bl1,
|
||||
props.bitcell.cell_1rw1r.pin.br1,
|
||||
props.bitcell.cell_1rw1r.pin.wl0,
|
||||
props.bitcell.cell_1rw1r.pin.wl1,
|
||||
props.bitcell.cell_1rw1r.pin.vdd,
|
||||
props.bitcell.cell_1rw1r.pin.gnd]
|
||||
|
||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
|
||||
|
|
@ -47,14 +56,15 @@ class replica_bitcell_1rw_1r(design.design):
|
|||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
pins = props.bitcell.cell_1rw1r.pin
|
||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
|
||||
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.bl0], self)
|
||||
graph.add_edge(pin_dict[pins.wl0], pin_dict[pins.br0], self)
|
||||
# Port 1 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import design
|
|||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer,drc,parameter
|
||||
from tech import cell_properties as props
|
||||
|
||||
class replica_bitcell_1w_1r(design.design):
|
||||
"""
|
||||
|
|
@ -17,7 +18,15 @@ class replica_bitcell_1w_1r(design.design):
|
|||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
pin_names = [props.bitcell.cell_1w1r.pin.bl0,
|
||||
props.bitcell.cell_1w1r.pin.br0,
|
||||
props.bitcell.cell_1w1r.pin.bl1,
|
||||
props.bitcell.cell_1w1r.pin.br1,
|
||||
props.bitcell.cell_1w1r.pin.wl0,
|
||||
props.bitcell.cell_1w1r.pin.wl1,
|
||||
props.bitcell.cell_1w1r.pin.vdd,
|
||||
props.bitcell.cell_1w1r.pin.gnd]
|
||||
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"])
|
||||
|
|
@ -47,13 +56,14 @@ class replica_bitcell_1w_1r(design.design):
|
|||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
debug.info(1,'Adding edges for {}'.format(inst_name))
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
pins = props.bitcell.cell_1w1r.pin
|
||||
#Edges hardcoded here. Essentially wl->bl/br for the read port.
|
||||
# Port 1 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
|
||||
# Port 0 is a write port, so its timing is not considered here.
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.bl1], self)
|
||||
graph.add_edge(pin_dict[pins.wl1], pin_dict[pins.br1], self)
|
||||
# Port 0 is a write port, so its timing is not considered here.
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class replica_pbitcell(design.design):
|
|||
|
||||
self.create_netlist()
|
||||
self.create_layout()
|
||||
self.add_boundary()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
|
|
@ -84,4 +85,4 @@ class replica_pbitcell(design.design):
|
|||
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
|
||||
self.copy_layout_pin(self.prbc_inst, "vdd")
|
||||
self.copy_layout_pin(self.prbc_inst, "gnd")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 """
|
||||
|
||||
self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"]
|
||||
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
|
||||
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power",
|
||||
"disabled_read0_power", "disabled_read1_power", "disabled_write0_power", "disabled_write1_power"]
|
||||
# self.voltage_when_names = ["volt_bl", "volt_br"]
|
||||
# self.bitline_delay_names = ["delay_bl", "delay_br"]
|
||||
|
||||
|
|
@ -108,6 +109,11 @@ class delay(simulation):
|
|||
self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3))
|
||||
self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
|
||||
self.read_lib_meas.append(power_measure("disabled_read1_power", "RISE", measure_scale=1e3))
|
||||
self.read_lib_meas[-1].meta_str = "disabled_read1"
|
||||
self.read_lib_meas.append(power_measure("disabled_read0_power", "FALL", measure_scale=1e3))
|
||||
self.read_lib_meas[-1].meta_str = "disabled_read0"
|
||||
|
||||
# This will later add a half-period to the spice time delay. Only for reading 0.
|
||||
for obj in self.read_lib_meas:
|
||||
if obj.meta_str is sram_op.READ_ZERO:
|
||||
|
|
@ -155,6 +161,11 @@ class delay(simulation):
|
|||
self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3))
|
||||
self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO
|
||||
|
||||
self.write_lib_meas.append(power_measure("disabled_write1_power", "RISE", measure_scale=1e3))
|
||||
self.write_lib_meas[-1].meta_str = "disabled_write1"
|
||||
self.write_lib_meas.append(power_measure("disabled_write0_power", "FALL", measure_scale=1e3))
|
||||
self.write_lib_meas[-1].meta_str = "disabled_write0"
|
||||
|
||||
write_measures = []
|
||||
write_measures.append(self.write_lib_meas)
|
||||
write_measures.append(self.create_write_bit_measures())
|
||||
|
|
@ -170,39 +181,40 @@ class delay(simulation):
|
|||
meas.targ_name_no_port))
|
||||
self.dout_volt_meas[-1].meta_str = meas.meta_str
|
||||
|
||||
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9)
|
||||
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name+"{}", "FALL", "RISE", measure_scale=1e9)
|
||||
self.sen_meas.meta_str = sram_op.READ_ZERO
|
||||
self.sen_meas.meta_add_delay = True
|
||||
self.dout_volt_meas.append(self.sen_meas)
|
||||
|
||||
return self.dout_volt_meas+[self.sen_meas]
|
||||
return self.dout_volt_meas
|
||||
|
||||
def create_read_bit_measures(self):
|
||||
""" Adds bit measurements for read0 and read1 cycles """
|
||||
|
||||
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||
self.read_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
|
||||
for cycle in meas_cycles:
|
||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||
for polarity,meas in single_bit_meas.items():
|
||||
meas.meta_str = cycle
|
||||
self.bit_meas[polarity].append(meas)
|
||||
self.read_bit_meas[polarity].append(meas)
|
||||
# Dictionary values are lists, reduce to a single list of measurements
|
||||
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
|
||||
return [meas for meas_list in self.read_bit_meas.values() for meas in meas_list]
|
||||
|
||||
def create_write_bit_measures(self):
|
||||
""" Adds bit measurements for write0 and write1 cycles """
|
||||
|
||||
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||
self.write_bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
|
||||
for cycle in meas_cycles:
|
||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||
for polarity,meas in single_bit_meas.items():
|
||||
meas.meta_str = cycle
|
||||
self.bit_meas[polarity].append(meas)
|
||||
self.write_bit_meas[polarity].append(meas)
|
||||
# Dictionary values are lists, reduce to a single list of measurements
|
||||
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
|
||||
return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list]
|
||||
|
||||
def get_bit_measures(self, meas_tag, probe_address, probe_data):
|
||||
"""
|
||||
|
|
@ -225,9 +237,10 @@ class delay(simulation):
|
|||
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
|
||||
|
||||
# Bit measures, measurements times to be defined later. The measurement names must be unique
|
||||
# but they is enforced externally
|
||||
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False)
|
||||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False)
|
||||
# but they is enforced externally. {} added to names to differentiate between ports allow the
|
||||
# measurements are independent of the ports
|
||||
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name)
|
||||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name)
|
||||
|
||||
return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas}
|
||||
|
||||
|
|
@ -267,10 +280,33 @@ class delay(simulation):
|
|||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
||||
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
sen_with_port = self.get_sen_name(self.graph.all_paths)
|
||||
if sen_with_port.endswith(str(port)):
|
||||
self.sen_name = sen_with_port[:-len(str(port))]
|
||||
else:
|
||||
self.sen_name = sen_with_port
|
||||
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
|
||||
|
||||
debug.info(2,"s_en name = {}".format(self.sen_name))
|
||||
|
||||
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
||||
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
||||
port_pos = -1-len(str(self.probe_data))-len(str(port))
|
||||
|
||||
if bl_name_port.endswith(str(port)+"_"+str(self.probe_data)):
|
||||
self.bl_name = bl_name_port[:port_pos] +"{}"+ bl_name_port[port_pos+len(str(port)):]
|
||||
elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||
self.bl_name = bl_name_port
|
||||
else:
|
||||
self.bl_name = bl_name_port
|
||||
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
||||
|
||||
if br_name_port.endswith(str(port)+"_"+str(self.probe_data)):
|
||||
self.br_name = br_name_port[:port_pos] +"{}"+ br_name_port[port_pos+len(str(port)):]
|
||||
elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0
|
||||
self.br_name = br_name_port
|
||||
else:
|
||||
self.br_name = br_name_port
|
||||
debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.")
|
||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||
else:
|
||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||
|
|
@ -283,8 +319,9 @@ class delay(simulation):
|
|||
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1)
|
||||
self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1)
|
||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||
|
||||
|
||||
def get_sen_name(self, paths):
|
||||
def get_sen_name(self, paths, assumed_port=None):
|
||||
"""
|
||||
Gets the signal name associated with the sense amp enable from input paths.
|
||||
Only expects a single path to contain the sen signal name.
|
||||
|
|
@ -674,8 +711,9 @@ class delay(simulation):
|
|||
if (time_out <= 0):
|
||||
debug.error("Timed out, could not find a feasible period.",2)
|
||||
|
||||
# Clear any write target ports and set read port
|
||||
self.targ_write_ports = [port]
|
||||
# Write ports are assumed non-critical to timing, so the first available is used
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
# Set target read port for simulation
|
||||
self.targ_read_ports = [port]
|
||||
|
||||
debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port))
|
||||
|
|
@ -689,7 +727,7 @@ class delay(simulation):
|
|||
if not success:
|
||||
feasible_period = 2 * feasible_period
|
||||
continue
|
||||
|
||||
|
||||
# Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews
|
||||
feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname]
|
||||
feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname]
|
||||
|
|
@ -756,12 +794,10 @@ class delay(simulation):
|
|||
# Loop through all targeted ports and collect delays and powers.
|
||||
result = [{} for i in self.all_ports]
|
||||
|
||||
|
||||
# First, check that the memory has the right values at the right times
|
||||
if not self.check_bit_measures():
|
||||
return(False,{})
|
||||
|
||||
for port in self.targ_write_ports:
|
||||
if not self.check_bit_measures(self.write_bit_meas, port):
|
||||
return(False,{})
|
||||
|
||||
debug.info(2, "Checking write values for port {}".format(port))
|
||||
write_port_dict = {}
|
||||
for measure in self.write_lib_meas:
|
||||
|
|
@ -773,6 +809,10 @@ class delay(simulation):
|
|||
|
||||
|
||||
for port in self.targ_read_ports:
|
||||
# First, check that the memory has the right values at the right times
|
||||
if not self.check_bit_measures(self.read_bit_meas, port):
|
||||
return(False,{})
|
||||
|
||||
debug.info(2, "Checking read delay values for port {}".format(port))
|
||||
# Check sen timing, then bitlines, then general measurements.
|
||||
if not self.check_sen_measure(port):
|
||||
|
|
@ -849,15 +889,15 @@ class delay(simulation):
|
|||
return dout_success
|
||||
|
||||
|
||||
def check_bit_measures(self):
|
||||
def check_bit_measures(self, bit_measures, port):
|
||||
"""
|
||||
Checks the measurements which represent the internal storage voltages
|
||||
at the end of the read cycle.
|
||||
"""
|
||||
success = False
|
||||
for polarity, meas_list in self.bit_meas.items():
|
||||
for polarity, meas_list in bit_measures.items():
|
||||
for meas in meas_list:
|
||||
val = meas.retrieve_measure()
|
||||
val = meas.retrieve_measure(port=port)
|
||||
debug.info(2,"{}={}".format(meas.name, val))
|
||||
if type(val) != float:
|
||||
continue
|
||||
|
|
@ -990,7 +1030,8 @@ class delay(simulation):
|
|||
|
||||
# Binary search algorithm to find the min period (max frequency) of input port
|
||||
time_out = 25
|
||||
self.targ_write_ports = [port]
|
||||
# Write ports are assumed non-critical to timing, so the first available is used
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
self.targ_read_ports = [port]
|
||||
while True:
|
||||
time_out -= 1
|
||||
|
|
@ -1088,7 +1129,8 @@ class delay(simulation):
|
|||
self.trimsp.set_configuration(self.num_banks,
|
||||
self.num_rows,
|
||||
self.num_cols,
|
||||
self.word_size)
|
||||
self.word_size,
|
||||
self.num_spare_rows)
|
||||
self.trimsp.trim(self.probe_address,self.probe_data)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
|
|
@ -1220,6 +1262,9 @@ class delay(simulation):
|
|||
write_port)
|
||||
self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1
|
||||
|
||||
self.add_noop_clock_one_port(write_port)
|
||||
self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times)-1
|
||||
|
||||
# This also ensures we will have a H->L transition on the next read
|
||||
self.add_read("R data 1 address {} to set dout caps".format(inverse_address),
|
||||
inverse_address,
|
||||
|
|
@ -1230,6 +1275,10 @@ class delay(simulation):
|
|||
read_port)
|
||||
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1
|
||||
|
||||
self.add_noop_clock_one_port(read_port)
|
||||
self.measure_cycles[read_port]["disabled_read0"] = len(self.cycle_times) - 1
|
||||
|
||||
|
||||
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
|
||||
|
||||
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
||||
|
|
@ -1239,12 +1288,19 @@ class delay(simulation):
|
|||
write_port)
|
||||
self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1
|
||||
|
||||
self.add_noop_clock_one_port(write_port)
|
||||
self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times)-1
|
||||
|
||||
self.add_write("W data 0 address {} to clear din caps".format(inverse_address),
|
||||
inverse_address,
|
||||
data_zeros,
|
||||
wmask_ones,
|
||||
write_port)
|
||||
|
||||
self.add_noop_clock_one_port(read_port)
|
||||
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
|
||||
|
||||
|
||||
# This also ensures we will have a L->H transition on the next read
|
||||
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address),
|
||||
inverse_address,
|
||||
|
|
@ -1278,8 +1334,8 @@ class delay(simulation):
|
|||
"""
|
||||
|
||||
# Using this requires setting at least one port to target for simulation.
|
||||
if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0:
|
||||
debug.error("No port selected for characterization.",1)
|
||||
if len(self.targ_write_ports) == 0 or len(self.targ_read_ports) == 0:
|
||||
debug.error("Write and read port must be specified for characterization.",1)
|
||||
self.set_stimulus_variables()
|
||||
|
||||
# Get any available read/write port in case only a single write or read ports is being characterized.
|
||||
|
|
|
|||
|
|
@ -5,23 +5,18 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys,re,shutil
|
||||
import copy
|
||||
import collections
|
||||
from design import design
|
||||
import debug
|
||||
import math
|
||||
import tech
|
||||
import random
|
||||
from .stimuli import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from .simulation import simulation
|
||||
from .delay import delay
|
||||
# from .delay import delay
|
||||
import graph_util
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class functional(simulation):
|
||||
"""
|
||||
Functions to write random data values to a random address then read them back and check
|
||||
|
|
@ -40,6 +35,9 @@ class functional(simulation):
|
|||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if not self.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
self.set_corner(corner)
|
||||
self.set_spice_constants()
|
||||
self.set_stimulus_variables()
|
||||
|
|
@ -57,7 +55,6 @@ class functional(simulation):
|
|||
self.read_check = []
|
||||
self.read_results = []
|
||||
|
||||
|
||||
def run(self, feasible_period=None):
|
||||
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
||||
self.period = feasible_period
|
||||
|
|
@ -82,10 +79,11 @@ class functional(simulation):
|
|||
for port in self.all_ports:
|
||||
checks = []
|
||||
if port in self.read_ports:
|
||||
checks.append((self.addr_value[port],"addr"))
|
||||
checks.append((self.addr_value[port], "addr"))
|
||||
if port in self.write_ports:
|
||||
checks.append((self.data_value[port],"data"))
|
||||
checks.append((self.wmask_value[port],"wmask"))
|
||||
checks.append((self.data_value[port], "data"))
|
||||
checks.append((self.wmask_value[port], "wmask"))
|
||||
checks.append((self.spare_wen_value[port], "spare_wen"))
|
||||
|
||||
for (val, name) in checks:
|
||||
debug.check(len(self.cycle_times)==len(val),
|
||||
|
|
@ -104,15 +102,15 @@ class functional(simulation):
|
|||
r_ops = ["noop", "read"]
|
||||
|
||||
# First cycle idle is always an idle cycle
|
||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current)
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment)
|
||||
|
||||
# 1. Write all the write ports first to seed a bunch of locations.
|
||||
for port in self.write_ports:
|
||||
addr = self.gen_addr()
|
||||
word = self.gen_data()
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port)
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||
self.stored_words[addr] = word
|
||||
|
||||
# All other read-only ports are noops.
|
||||
|
|
@ -131,7 +129,7 @@ class functional(simulation):
|
|||
if port in self.write_ports:
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current)
|
||||
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
||||
self.add_read_one_port(comment, addr, port)
|
||||
self.add_read_check(word, port)
|
||||
self.cycle_times.append(self.t_current)
|
||||
|
|
@ -160,13 +158,13 @@ class functional(simulation):
|
|||
self.add_noop_one_port(port)
|
||||
else:
|
||||
word = self.gen_data()
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port)
|
||||
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
|
||||
self.stored_words[addr] = word
|
||||
w_addrs.append(addr)
|
||||
elif op == "partial_write":
|
||||
# write only to a word that's been written to
|
||||
(addr,old_word) = self.get_data()
|
||||
(addr, old_word) = self.get_data()
|
||||
# two ports cannot write to the same address
|
||||
if addr in w_addrs:
|
||||
self.add_noop_one_port(port)
|
||||
|
|
@ -179,7 +177,7 @@ class functional(simulation):
|
|||
self.stored_words[addr] = new_word
|
||||
w_addrs.append(addr)
|
||||
else:
|
||||
(addr,word) = random.choice(list(self.stored_words.items()))
|
||||
(addr, word) = random.choice(list(self.stored_words.items()))
|
||||
# The write driver is not sized sufficiently to drive through the two
|
||||
# bitcell access transistors to the read port. So, for now, we do not allow
|
||||
# a simultaneous write and read to the same address on different ports. This
|
||||
|
|
@ -187,7 +185,7 @@ class functional(simulation):
|
|||
if addr in w_addrs:
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current)
|
||||
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
|
||||
self.add_read_one_port(comment, addr, port)
|
||||
self.add_read_check(word, port)
|
||||
|
||||
|
|
@ -195,7 +193,7 @@ class functional(simulation):
|
|||
self.t_current += self.period
|
||||
|
||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current)
|
||||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment)
|
||||
|
||||
def gen_masked_data(self, old_word, word, wmask):
|
||||
|
|
@ -209,7 +207,7 @@ class functional(simulation):
|
|||
if wmask[bit] == "0":
|
||||
lower = bit * self.write_size
|
||||
upper = lower + self.write_size - 1
|
||||
new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:]
|
||||
new_word = new_word[:lower] + old_word[lower:upper + 1] + new_word[upper + 1:]
|
||||
|
||||
return new_word
|
||||
|
||||
|
|
@ -219,15 +217,15 @@ class functional(simulation):
|
|||
self.check
|
||||
except:
|
||||
self.check = 0
|
||||
self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check])
|
||||
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
|
||||
self.check += 1
|
||||
|
||||
def read_stim_results(self):
|
||||
# Extract dout values from spice timing.lis
|
||||
for (word, dout_port, eo_period, check) in self.read_check:
|
||||
sp_read_value = ""
|
||||
for bit in range(self.word_size):
|
||||
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check))
|
||||
if value > self.v_high:
|
||||
sp_read_value = "1" + sp_read_value
|
||||
elif value < self.v_low:
|
||||
|
|
@ -278,17 +276,24 @@ class functional(simulation):
|
|||
# wmask must be reversed since a python list goes right to left and sram bits go left to right.
|
||||
return wmask[::-1]
|
||||
|
||||
|
||||
def gen_data(self):
|
||||
""" Generates a random word to write. """
|
||||
random_value = random.randint(0,(2**self.word_size)-1)
|
||||
data_bits = self.convert_to_bin(random_value,False)
|
||||
if not self.num_spare_cols:
|
||||
random_value = random.randint(0, (2 ** self.word_size) - 1)
|
||||
else:
|
||||
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
|
||||
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
|
||||
random_value = random_value1 + random_value2
|
||||
data_bits = self.convert_to_bin(random_value, False)
|
||||
return data_bits
|
||||
|
||||
def gen_addr(self):
|
||||
""" Generates a random address value to write to. """
|
||||
random_value = random.randint(0,(2**self.addr_size)-1)
|
||||
addr_bits = self.convert_to_bin(random_value,True)
|
||||
if self.num_spare_rows==0:
|
||||
random_value = random.randint(0, (2 ** self.addr_size) - 1)
|
||||
else:
|
||||
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
|
||||
addr_bits = self.convert_to_bin(random_value, True)
|
||||
return addr_bits
|
||||
|
||||
def get_data(self):
|
||||
|
|
@ -296,37 +301,36 @@ class functional(simulation):
|
|||
# Used for write masks since they should be writing to previously written addresses
|
||||
addr = random.choice(list(self.stored_words.keys()))
|
||||
word = self.stored_words[addr]
|
||||
return (addr,word)
|
||||
return (addr, word)
|
||||
|
||||
def convert_to_bin(self,value,is_addr):
|
||||
def convert_to_bin(self, value, is_addr):
|
||||
""" Converts addr & word to usable binary values. """
|
||||
new_value = str.replace(bin(value),"0b","")
|
||||
new_value = str.replace(bin(value), "0b", "")
|
||||
if(is_addr):
|
||||
expected_value = self.addr_size
|
||||
else:
|
||||
|
||||
expected_value = self.word_size
|
||||
for i in range (expected_value - len(new_value)):
|
||||
expected_value = self.word_size + self.num_spare_cols
|
||||
for i in range(expected_value - len(new_value)):
|
||||
new_value = "0" + new_value
|
||||
|
||||
#print("Binary Conversion: {} to {}".format(value, new_value))
|
||||
return new_value
|
||||
# print("Binary Conversion: {} to {}".format(value, new_value))
|
||||
return new_value
|
||||
|
||||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
||||
self.sf = open(temp_stim,"w")
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf,self.corner)
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
#Write include statements
|
||||
# Write include statements
|
||||
self.stim.write_include(self.sp_file)
|
||||
|
||||
#Write Vdd/Gnd statements
|
||||
# Write Vdd/Gnd statements
|
||||
self.sf.write("\n* Global Power Supplies\n")
|
||||
self.stim.write_supply()
|
||||
|
||||
#Instantiate the SRAM
|
||||
# Instantiate the SRAM
|
||||
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||
self.stim.inst_model(pins=self.pins,
|
||||
model_name=self.sram.name)
|
||||
|
|
@ -334,7 +338,7 @@ class functional(simulation):
|
|||
# Add load capacitance to each of the read ports
|
||||
self.sf.write("\n* SRAM output loads\n")
|
||||
for port in self.read_ports:
|
||||
for bit in range(self.word_size):
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
|
||||
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
|
||||
|
||||
|
|
@ -351,10 +355,10 @@ class functional(simulation):
|
|||
for comment in self.fn_cycle_comments:
|
||||
self.sf.write("*{}\n".format(comment))
|
||||
|
||||
# Generate data input bits
|
||||
# Generate data input bits
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size):
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
||||
|
|
@ -367,10 +371,10 @@ class functional(simulation):
|
|||
# Generate control signals
|
||||
self.sf.write("\n * Generation of control signals\n")
|
||||
for port in self.all_ports:
|
||||
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05)
|
||||
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
for port in self.readwrite_ports:
|
||||
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05)
|
||||
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
# Generate wmask bits
|
||||
for port in self.write_ports:
|
||||
|
|
@ -383,6 +387,15 @@ class functional(simulation):
|
|||
self.stim.gen_pwl(sig_name, self.cycle_times, self.wmask_values[port][bit], self.period,
|
||||
self.slew, 0.05)
|
||||
|
||||
# Generate spare enable bits (for spare cols)
|
||||
for port in self.write_ports:
|
||||
if self.num_spare_cols:
|
||||
self.sf.write("\n* Generation of spare enable signals\n")
|
||||
for bit in range(self.num_spare_cols):
|
||||
sig_name = "SPARE_WEN{0}_{1} ".format(port, bit)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.spare_wen_values[port][bit], self.period,
|
||||
self.slew, 0.05)
|
||||
|
||||
# Generate CLK signals
|
||||
for port in self.all_ports:
|
||||
self.stim.gen_pulse(sig_name="{0}{1}".format("clk", port),
|
||||
|
|
@ -396,11 +409,11 @@ class functional(simulation):
|
|||
# Generate dout value measurements
|
||||
self.sf.write("\n * Generation of dout measurements\n")
|
||||
for (word, dout_port, eo_period, check) in self.read_check:
|
||||
t_intital = eo_period - 0.01*self.period
|
||||
t_final = eo_period + 0.01*self.period
|
||||
for bit in range(self.word_size):
|
||||
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
|
||||
dout="{0}_{1}".format(dout_port,bit),
|
||||
t_intital = eo_period - 0.01 * self.period
|
||||
t_final = eo_period + 0.01 * self.period
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
|
||||
dout="{0}_{1}".format(dout_port, bit),
|
||||
t_intital=t_intital,
|
||||
t_final=t_final)
|
||||
|
||||
|
|
@ -430,7 +443,7 @@ class functional(simulation):
|
|||
# Generate new graph every analysis as edges might change depending on test bit
|
||||
self.graph = graph_util.timing_graph()
|
||||
self.sram_spc_name = "X{}".format(self.sram.name)
|
||||
self.sram.build_graph(self.graph,self.sram_spc_name,self.pins)
|
||||
self.sram.build_graph(self.graph, self.sram_spc_name, self.pins)
|
||||
|
||||
# FIXME: refactor to share with delay.py
|
||||
def set_internal_spice_names(self):
|
||||
|
|
@ -438,17 +451,17 @@ class functional(simulation):
|
|||
|
||||
# For now, only testing these using first read port.
|
||||
port = self.read_ports[0]
|
||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||
self.graph.get_all_paths('{}{}'.format("clk", port),
|
||||
'{}{}_{}'.format(self.dout_name, port, 0).lower())
|
||||
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
debug.info(2,"s_en name = {}".format(self.sen_name))
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
debug.info(2, "s_en name = {}".format(self.sen_name))
|
||||
|
||||
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||
self.bl_name, self.br_name = self.get_bl_name(self.graph.all_paths, port)
|
||||
debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name))
|
||||
|
||||
self.q_name,self.qbar_name = self.get_bit_name()
|
||||
debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name))
|
||||
self.q_name, self.qbar_name = self.get_bit_name()
|
||||
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
|
||||
|
||||
def get_bit_name(self):
|
||||
""" Get a bit cell name """
|
||||
|
|
@ -456,10 +469,10 @@ class functional(simulation):
|
|||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={}").format(storage_names))
|
||||
q_name = cell_name+'.'+str(storage_names[0])
|
||||
qbar_name = cell_name+'.'+str(storage_names[1])
|
||||
q_name = cell_name + '.' + str(storage_names[0])
|
||||
qbar_name = cell_name + '.' + str(storage_names[1])
|
||||
|
||||
return (q_name,qbar_name)
|
||||
return (q_name, qbar_name)
|
||||
|
||||
# FIXME: refactor to share with delay.py
|
||||
def get_sen_name(self, paths):
|
||||
|
|
@ -469,29 +482,28 @@ class functional(simulation):
|
|||
"""
|
||||
|
||||
sa_mods = factory.get_mods(OPTS.sense_amp)
|
||||
# Any sense amp instantiated should be identical, any change to that
|
||||
# Any sense amp instantiated should be identical, any change to that
|
||||
# will require some identification to determine the mod desired.
|
||||
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
|
||||
enable_name = sa_mods[0].get_enable_name()
|
||||
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
|
||||
return sen_name
|
||||
return sen_name
|
||||
|
||||
# FIXME: refactor to share with delay.py
|
||||
def get_bl_name(self, paths, port):
|
||||
"""Gets the signal name associated with the bitlines in the bank."""
|
||||
|
||||
cell_mod = factory.create(module_type=OPTS.bitcell)
|
||||
cell_mod = factory.create(module_type=OPTS.bitcell)
|
||||
cell_bl = cell_mod.get_bl_name(port)
|
||||
cell_br = cell_mod.get_br_name(port)
|
||||
|
||||
bl_found = False
|
||||
# Only a single path should contain a single s_en name. Anything else is an error.
|
||||
bl_names = []
|
||||
exclude_set = self.get_bl_name_search_exclusions()
|
||||
for int_net in [cell_bl, cell_br]:
|
||||
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
|
||||
|
||||
return bl_names[0], bl_names[1]
|
||||
return bl_names[0], bl_names[1]
|
||||
|
||||
def get_bl_name_search_exclusions(self):
|
||||
"""Gets the mods as a set which should be excluded while searching for name."""
|
||||
|
|
@ -500,9 +512,9 @@ class functional(simulation):
|
|||
# so it makes the search awkward
|
||||
return set(factory.get_mods(OPTS.replica_bitline))
|
||||
|
||||
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
|
||||
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
|
||||
"""
|
||||
Finds a single alias for the int_net in given paths.
|
||||
Finds a single alias for the int_net in given paths.
|
||||
More or less hits cause an error
|
||||
"""
|
||||
|
||||
|
|
@ -510,14 +522,14 @@ class functional(simulation):
|
|||
for path in paths:
|
||||
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
|
||||
if net_found and len(aliases) >= 1:
|
||||
debug.error('Found multiple paths with {} net.'.format(int_net),1)
|
||||
debug.error('Found multiple paths with {} net.'.format(int_net), 1)
|
||||
elif len(aliases) > 1:
|
||||
debug.error('Found multiple {} nets in single path.'.format(int_net),1)
|
||||
debug.error('Found multiple {} nets in single path.'.format(int_net), 1)
|
||||
elif not net_found and len(aliases) == 1:
|
||||
path_net_name = aliases[0]
|
||||
net_found = True
|
||||
if not net_found:
|
||||
debug.error("Could not find {} net in timing paths.".format(int_net),1)
|
||||
debug.error("Could not find {} net in timing paths.".format(int_net), 1)
|
||||
|
||||
return path_net_name
|
||||
return path_net_name
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class lib:
|
|||
""" Determine the load/slews if they aren't specified in the config file. """
|
||||
# These are the parameters to determine the table sizes
|
||||
#self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8])
|
||||
self.load_scales = np.array([0.25, 1, 8])
|
||||
self.load_scales = np.array([0.25, 1, 4])
|
||||
#self.load_scales = np.array([0.25, 1])
|
||||
self.load = tech.spice["dff_in_cap"]
|
||||
self.loads = self.load_scales*self.load
|
||||
|
|
@ -66,25 +66,57 @@ class lib:
|
|||
self.supply_voltages = OPTS.supply_voltages
|
||||
self.process_corners = OPTS.process_corners
|
||||
|
||||
# Enumerate all possible corners
|
||||
# Corner values
|
||||
min_temperature = min(self.temperatures)
|
||||
nom_temperature = tech.spice["nom_temperature"]
|
||||
max_temperature = max(self.temperatures)
|
||||
min_supply = min(self.supply_voltages)
|
||||
nom_supply = tech.spice["nom_supply_voltage"]
|
||||
max_supply = max(self.supply_voltages)
|
||||
min_process = "FF"
|
||||
nom_process = "TT"
|
||||
max_process = "SS"
|
||||
|
||||
self.corners = []
|
||||
self.lib_files = []
|
||||
for proc in self.process_corners:
|
||||
for temp in self.temperatures:
|
||||
for volt in self.supply_voltages:
|
||||
self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name,
|
||||
proc,
|
||||
volt,
|
||||
temp)
|
||||
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
|
||||
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
|
||||
|
||||
# Nominal corner
|
||||
corner_set = set()
|
||||
nom_corner = (nom_process, nom_supply, nom_temperature)
|
||||
corner_set.add(nom_corner)
|
||||
if not OPTS.nominal_corner_only:
|
||||
# Temperature corners
|
||||
corner_set.add((nom_process, nom_supply, min_temperature))
|
||||
corner_set.add((nom_process, nom_supply, max_temperature))
|
||||
# Supply corners
|
||||
corner_set.add((nom_process, min_supply, nom_temperature))
|
||||
corner_set.add((nom_process, max_supply, nom_temperature))
|
||||
# Process corners
|
||||
corner_set.add((min_process, nom_supply, nom_temperature))
|
||||
corner_set.add((max_process, nom_supply, nom_temperature))
|
||||
|
||||
# Enforce that nominal corner is the first to be characterized
|
||||
self.add_corner(*nom_corner)
|
||||
corner_set.remove(nom_corner)
|
||||
for corner_tuple in corner_set:
|
||||
self.add_corner(*corner_tuple)
|
||||
|
||||
def add_corner(self, proc, volt, temp):
|
||||
self.corner_name = "{0}_{1}_{2}V_{3}C".format(self.sram.name,
|
||||
proc,
|
||||
volt,
|
||||
temp)
|
||||
self.corner_name = self.corner_name.replace(".","p") # Remove decimals
|
||||
lib_name = self.out_dir+"{}.lib".format(self.corner_name)
|
||||
|
||||
# A corner is a tuple of PVT
|
||||
self.corners.append((proc, volt, temp))
|
||||
self.lib_files.append(lib_name)
|
||||
|
||||
# A corner is a tuple of PVT
|
||||
self.corners.append((proc, volt, temp))
|
||||
self.lib_files.append(lib_name)
|
||||
|
||||
def characterize_corners(self):
|
||||
""" Characterize the list of corners. """
|
||||
debug.info(1,"Characterizing corners: " + str(self.corners))
|
||||
for (self.corner,lib_name) in zip(self.corners,self.lib_files):
|
||||
debug.info(1,"Corner: " + str(self.corner))
|
||||
(self.process, self.voltage, self.temperature) = self.corner
|
||||
|
|
@ -149,17 +181,20 @@ class lib:
|
|||
self.lib.write(" dont_touch : true;\n")
|
||||
self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height))
|
||||
|
||||
#Build string of all control signals.
|
||||
self.write_pg_pin()
|
||||
|
||||
#Build string of all control signals.
|
||||
control_str = 'csb0' #assume at least 1 port
|
||||
for i in range(1, self.total_port_num):
|
||||
control_str += ' & csb{0}'.format(i)
|
||||
|
||||
# Leakage is included in dynamic when macro is enabled
|
||||
self.lib.write(" leakage_power () {\n")
|
||||
self.lib.write(" when : \"{0}\";\n".format(control_str))
|
||||
# 'when' condition unnecessary when cs pin does not turn power to devices
|
||||
# self.lib.write(" when : \"{0}\";\n".format(control_str))
|
||||
self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" cell_leakage_power : {};\n".format(0))
|
||||
self.lib.write(" cell_leakage_power : {};\n".format(self.char_sram_results["leakage_power"]))
|
||||
|
||||
|
||||
def write_units(self):
|
||||
|
|
@ -208,6 +243,9 @@ class lib:
|
|||
self.lib.write(" default_max_fanout : 4.0 ;\n")
|
||||
self.lib.write(" default_connection_class : universal ;\n\n")
|
||||
|
||||
self.lib.write(" voltage_map ( VDD, {} );\n".format(tech.spice["nom_supply_voltage"]))
|
||||
self.lib.write(" voltage_map ( GND, 0 );\n\n")
|
||||
|
||||
def create_list(self,values):
|
||||
""" Helper function to create quoted, line wrapped list """
|
||||
list_values = ", ".join(str(v) for v in values)
|
||||
|
|
@ -484,42 +522,69 @@ class lib:
|
|||
if port in self.write_ports:
|
||||
if port in self.read_ports:
|
||||
web_name = " & !web{0}".format(port)
|
||||
avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])
|
||||
write1_power = np.mean(self.char_port_results[port]["write1_power"])
|
||||
write0_power = np.mean(self.char_port_results[port]["write0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!csb{0} & clk{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(write1_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(write0_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
# Disabled power.
|
||||
disabled_write1_power = np.mean(self.char_port_results[port]["disabled_write1_power"])
|
||||
disabled_write0_power = np.mean(self.char_port_results[port]["disabled_write0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write1_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_write0_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
if port in self.read_ports:
|
||||
if port in self.write_ports:
|
||||
web_name = " & web{0}".format(port)
|
||||
avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])
|
||||
read1_power = np.mean(self.char_port_results[port]["read1_power"])
|
||||
read0_power = np.mean(self.char_port_results[port]["read0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!csb{0} & !clk{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" when : \"!csb{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(read1_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(read0_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
# Have 0 internal power when disabled, this will be represented as leakage power.
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"csb{0}\"; \n".format(port))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"0\");\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"0\");\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
# Disabled power.
|
||||
disabled_read1_power = np.mean(self.char_port_results[port]["disabled_read1_power"])
|
||||
disabled_read0_power = np.mean(self.char_port_results[port]["disabled_read0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"csb{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read1_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" fall_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0:.6e}\");\n".format(disabled_read0_power))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
|
||||
def write_pg_pin(self):
|
||||
self.lib.write(" pg_pin(vdd) {\n")
|
||||
self.lib.write(" voltage_name : VDD;\n")
|
||||
self.lib.write(" pg_type : primary_power;\n")
|
||||
self.lib.write(" }\n\n")
|
||||
self.lib.write(" pg_pin(gnd) {\n")
|
||||
self.lib.write(" voltage_name : GND;\n")
|
||||
self.lib.write(" pg_type : primary_ground;\n")
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
def compute_delay(self):
|
||||
"""Compute SRAM delays for current corner"""
|
||||
self.d = delay(self.sram, self.sp_file, self.corner)
|
||||
|
|
@ -527,7 +592,10 @@ class lib:
|
|||
char_results = self.d.analytical_delay(self.slews,self.loads)
|
||||
self.char_sram_results, self.char_port_results = char_results
|
||||
else:
|
||||
probe_address = "1" * self.sram.addr_size
|
||||
if (self.sram.num_spare_rows == 0):
|
||||
probe_address = "1" * self.sram.addr_size
|
||||
else:
|
||||
probe_address = "0" + "1" * (self.sram.addr_size - 1)
|
||||
probe_data = self.sram.word_size - 1
|
||||
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||
self.char_sram_results, self.char_port_results = char_results
|
||||
|
|
@ -589,17 +657,12 @@ class lib:
|
|||
))
|
||||
|
||||
# information of checks
|
||||
from hierarchy_design import total_drc_errors
|
||||
from hierarchy_design import total_lvs_errors
|
||||
DRC = 'skipped'
|
||||
LVS = 'skipped'
|
||||
if OPTS.check_lvsdrc:
|
||||
DRC = str(total_drc_errors)
|
||||
LVS = str(total_lvs_errors)
|
||||
|
||||
datasheet.write("{0},{1},".format(DRC, LVS))
|
||||
# run it only the first time
|
||||
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||
|
||||
# write area
|
||||
datasheet.write(str(self.sram.width * self.sram.height)+',')
|
||||
datasheet.write(str(self.sram.width * self.sram.height) + ',')
|
||||
|
||||
# write timing information for all ports
|
||||
for port in self.all_ports:
|
||||
#din timings
|
||||
|
|
|
|||
|
|
@ -25,18 +25,23 @@ class simulation():
|
|||
self.word_size = self.sram.word_size
|
||||
self.addr_size = self.sram.addr_size
|
||||
self.write_size = self.sram.write_size
|
||||
self.num_spare_rows = self.sram.num_spare_rows
|
||||
if not self.sram.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = self.sram.num_spare_cols
|
||||
self.sp_file = spfile
|
||||
|
||||
self.all_ports = self.sram.all_ports
|
||||
self.readwrite_ports = self.sram.readwrite_ports
|
||||
self.read_ports = self.sram.read_ports
|
||||
self.write_ports = self.sram.write_ports
|
||||
self.words_per_row = self.sram.words_per_row
|
||||
if self.write_size:
|
||||
self.num_wmasks = int(self.word_size/self.write_size)
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
|
||||
def set_corner(self,corner):
|
||||
""" Set the corner values """
|
||||
self.corner = corner
|
||||
|
|
@ -59,10 +64,10 @@ class simulation():
|
|||
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
||||
abits=self.addr_size,
|
||||
dbits=self.word_size)
|
||||
dbits=self.word_size + self.num_spare_cols)
|
||||
debug.check(len(self.sram.pins) == len(self.pins),
|
||||
"Number of pins generated for characterization \
|
||||
do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
|
||||
do not match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,
|
||||
self.pins))
|
||||
#This is TODO once multiport control has been finalized.
|
||||
#self.control_name = "CSB"
|
||||
|
|
@ -80,11 +85,13 @@ class simulation():
|
|||
self.addr_value = {port:[] for port in self.all_ports}
|
||||
self.data_value = {port:[] for port in self.write_ports}
|
||||
self.wmask_value = {port:[] for port in self.write_ports}
|
||||
self.spare_wen_value = {port:[] for port in self.write_ports}
|
||||
|
||||
# Three dimensional list to handle each addr and data bits for each port over the number of checks
|
||||
self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports}
|
||||
self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports}
|
||||
self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports}
|
||||
self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports}
|
||||
self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports}
|
||||
|
||||
# For generating comments in SPICE stimulus
|
||||
self.cycle_comments = []
|
||||
|
|
@ -111,10 +118,10 @@ class simulation():
|
|||
|
||||
def add_data(self, data, port):
|
||||
""" Add the array of data values """
|
||||
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
||||
debug.check(len(data)==(self.word_size + self.num_spare_cols), "Invalid data word size.")
|
||||
|
||||
self.data_value[port].append(data)
|
||||
bit = self.word_size - 1
|
||||
bit = self.word_size + self.num_spare_cols - 1
|
||||
for c in data:
|
||||
if c=="0":
|
||||
self.data_values[port][bit].append(0)
|
||||
|
|
@ -124,7 +131,6 @@ class simulation():
|
|||
debug.error("Non-binary data string",1)
|
||||
bit -= 1
|
||||
|
||||
|
||||
def add_address(self, address, port):
|
||||
""" Add the array of address values """
|
||||
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||
|
|
@ -135,7 +141,7 @@ class simulation():
|
|||
if c=="0":
|
||||
self.addr_values[port][bit].append(0)
|
||||
elif c=="1":
|
||||
self.addr_values[port][bit].append(1)
|
||||
self.addr_values[port][bit].append(1)
|
||||
else:
|
||||
debug.error("Non-binary address string",1)
|
||||
bit -= 1
|
||||
|
|
@ -156,7 +162,21 @@ class simulation():
|
|||
debug.error("Non-binary wmask string", 1)
|
||||
bit -= 1
|
||||
|
||||
|
||||
def add_spare_wen(self, spare_wen, port):
|
||||
""" Add the array of spare write enable values (for spare cols) """
|
||||
debug.check(len(spare_wen) == self.num_spare_cols, "Invalid spare enable size.")
|
||||
|
||||
self.spare_wen_value[port].append(spare_wen)
|
||||
bit = self.num_spare_cols - 1
|
||||
for c in spare_wen:
|
||||
if c == "0":
|
||||
self.spare_wen_values[port][bit].append(0)
|
||||
elif c == "1":
|
||||
self.spare_wen_values[port][bit].append(1)
|
||||
else:
|
||||
debug.error("Non-binary spare enable signal string", 1)
|
||||
bit -= 1
|
||||
|
||||
def add_write(self, comment, address, data, wmask, port):
|
||||
""" Add the control values for a write cycle. """
|
||||
debug.check(port in self.write_ports,
|
||||
|
|
@ -172,7 +192,8 @@ class simulation():
|
|||
self.add_control_one_port(port, "write")
|
||||
self.add_data(data,port)
|
||||
self.add_address(address,port)
|
||||
self.add_wmask(wmask,port)
|
||||
self.add_wmask(wmask,port)
|
||||
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
|
|
@ -191,19 +212,20 @@ class simulation():
|
|||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
self.add_control_one_port(port, "read")
|
||||
self.add_address(address, port)
|
||||
|
||||
self.add_address(address, port)
|
||||
|
||||
# If the port is also a readwrite then add
|
||||
# the same value as previous cycle
|
||||
if port in self.write_ports:
|
||||
try:
|
||||
self.add_data(self.data_value[port][-1], port)
|
||||
except:
|
||||
self.add_data("0"*self.word_size, port)
|
||||
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||
try:
|
||||
self.add_wmask(self.wmask_value[port][-1], port)
|
||||
except:
|
||||
self.add_wmask("0"*self.num_wmasks, port)
|
||||
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
|
|
@ -234,6 +256,7 @@ class simulation():
|
|||
self.add_data(data, port)
|
||||
self.add_address(address, port)
|
||||
self.add_wmask(wmask, port)
|
||||
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||
|
||||
def add_read_one_port(self, comment, address, port):
|
||||
""" Add the control values for a read cycle. Does not increment the period. """
|
||||
|
|
@ -245,23 +268,24 @@ class simulation():
|
|||
|
||||
self.add_control_one_port(port, "read")
|
||||
self.add_address(address, port)
|
||||
|
||||
# If the port is also a readwrite then add
|
||||
# the same value as previous cycle
|
||||
if port in self.write_ports:
|
||||
try:
|
||||
self.add_data(self.data_value[port][-1], port)
|
||||
except:
|
||||
self.add_data("0"*self.word_size, port)
|
||||
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||
try:
|
||||
self.add_wmask(self.wmask_value[port][-1], port)
|
||||
except:
|
||||
self.add_wmask("0"*self.num_wmasks, port)
|
||||
|
||||
self.add_wmask("0"*self.num_wmasks, port)
|
||||
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||
|
||||
def add_noop_one_port(self, port):
|
||||
""" Add the control values for a noop to a single port. Does not increment the period. """
|
||||
self.add_control_one_port(port, "noop")
|
||||
|
||||
|
||||
try:
|
||||
self.add_address(self.addr_value[port][-1], port)
|
||||
except:
|
||||
|
|
@ -273,11 +297,29 @@ class simulation():
|
|||
try:
|
||||
self.add_data(self.data_value[port][-1], port)
|
||||
except:
|
||||
self.add_data("0"*self.word_size, port)
|
||||
self.add_data("0"*(self.word_size + self.num_spare_cols), port)
|
||||
try:
|
||||
self.add_wmask(self.wmask_value[port][-1], port)
|
||||
except:
|
||||
self.add_wmask("0"*self.num_wmasks, port)
|
||||
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||
|
||||
def add_noop_clock_one_port(self, port):
|
||||
""" Add the control values for a noop to a single port. Increments the period. """
|
||||
debug.info(2, 'Clock only on port {}'.format(port))
|
||||
self.fn_cycle_comments.append('Clock only on port {}'.format(port))
|
||||
self.append_cycle_comment(port, 'Clock only on port {}'.format(port))
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
||||
self.add_noop_one_port(port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
||||
|
||||
def append_cycle_comment(self, port, comment):
|
||||
"""Add comment to list to be printed in stimulus file"""
|
||||
|
|
@ -352,6 +394,11 @@ class simulation():
|
|||
for port in write_index:
|
||||
for bit in range(self.num_wmasks):
|
||||
pin_names.append("WMASK{0}_{1}".format(port,bit))
|
||||
|
||||
if self.num_spare_cols:
|
||||
for port in write_index:
|
||||
for bit in range(self.num_spare_cols):
|
||||
pin_names.append("SPARE_WEN{0}_{1}".format(port,bit))
|
||||
|
||||
for read_output in read_index:
|
||||
for i in range(dbits):
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ class stimuli():
|
|||
self.sf = stim_file
|
||||
|
||||
(self.process, self.voltage, self.temperature) = corner
|
||||
try:
|
||||
self.device_libraries = tech.spice["fet_libraries"][self.process]
|
||||
except:
|
||||
debug.info(2, "Not using spice library")
|
||||
self.device_models = tech.spice["fet_models"][self.process]
|
||||
|
||||
self.sram_name = "Xsram"
|
||||
|
|
@ -270,8 +274,17 @@ class stimuli():
|
|||
|
||||
def write_include(self, circuit):
|
||||
"""Writes include statements, inputs are lists of model files"""
|
||||
|
||||
includes = self.device_models + [circuit]
|
||||
self.sf.write("* {} process corner\n".format(self.process))
|
||||
if OPTS.tech_name == "sky130":
|
||||
libraries = self.device_libraries
|
||||
for item in list(libraries):
|
||||
if os.path.isfile(item[0]):
|
||||
self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1]))
|
||||
else:
|
||||
debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0]))
|
||||
|
||||
for item in list(includes):
|
||||
if os.path.isfile(item):
|
||||
self.sf.write(".include \"{0}\"\n".format(item))
|
||||
|
|
@ -312,7 +325,9 @@ class stimuli():
|
|||
OPTS.openram_temp)
|
||||
valid_retcode=0
|
||||
else:
|
||||
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
||||
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
||||
# Measurements can't be made with a raw file set in ngspice
|
||||
# -r {2}timing.raw
|
||||
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,
|
||||
temp_stim,
|
||||
OPTS.openram_temp)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
import debug
|
||||
from math import log
|
||||
from math import log,ceil
|
||||
import re
|
||||
|
||||
class trim_spice():
|
||||
|
|
@ -42,7 +42,7 @@ class trim_spice():
|
|||
self.word_size = word_size
|
||||
|
||||
self.words_per_row = self.num_columns / self.word_size
|
||||
self.row_addr_size = int(log(self.num_rows, 2))
|
||||
self.row_addr_size = ceil(log(self.num_rows, 2))
|
||||
self.col_addr_size = int(log(self.words_per_row, 2))
|
||||
self.bank_addr_size = self.col_addr_size + self.row_addr_size
|
||||
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
# All rights reserved.
|
||||
#
|
||||
import globals
|
||||
import design
|
||||
from math import log
|
||||
import design
|
||||
from tech import GDS,layer,spice,parameter
|
||||
from tech import GDS, layer, spice, parameter
|
||||
from tech import cell_properties as props
|
||||
import utils
|
||||
|
||||
|
||||
class dff(design.design):
|
||||
"""
|
||||
Memory address flip-flop
|
||||
"""
|
||||
if not props.dff.use_custom_ports:
|
||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
clk_pin = "clk"
|
||||
else:
|
||||
pin_names = props.dff.custom_port_list
|
||||
type_list = props.dff.custom_type_list
|
||||
clk_pin = props.dff.clk_pin
|
||||
|
||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
||||
(width, height) = utils.get_libcell_size("dff",
|
||||
GDS["unit"],
|
||||
layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
||||
|
||||
def __init__(self, name="dff"):
|
||||
|
|
@ -54,7 +61,7 @@ class dff(design.design):
|
|||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||
return parameter["dff_clk_cin"]
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
|
|
@ -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 design
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from tech import GDS,layer
|
||||
from tech import cell_properties as props
|
||||
|
||||
class write_driver(design.design):
|
||||
"""
|
||||
|
|
@ -18,10 +20,20 @@ class write_driver(design.design):
|
|||
the technology library.
|
||||
"""
|
||||
|
||||
pin_names = ["din", "bl", "br", "en", "vdd", "gnd"]
|
||||
pin_names = [props.write_driver.pin.din,
|
||||
props.write_driver.pin.bl,
|
||||
props.write_driver.pin.br,
|
||||
props.write_driver.pin.en,
|
||||
props.write_driver.pin.vdd,
|
||||
props.write_driver.pin.gnd]
|
||||
|
||||
type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||
if not OPTS.netlist_only:
|
||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||
else:
|
||||
(width,height) = (0,0)
|
||||
pin_map = []
|
||||
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
|
|
@ -32,6 +44,20 @@ class write_driver(design.design):
|
|||
self.pin_map = write_driver.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def get_bl_names(self):
|
||||
return props.write_driver.pin.bl
|
||||
|
||||
def get_br_names(self):
|
||||
return props.write_driver.pin.br
|
||||
|
||||
@property
|
||||
def din_name(self):
|
||||
return props.write_driver.pin.din
|
||||
|
||||
@property
|
||||
def en_name(self):
|
||||
return props.write_driver.pin.en
|
||||
|
||||
def get_w_en_cin(self):
|
||||
"""Get the relative capacitance of a single input"""
|
||||
# This is approximated from SCMOS. It has roughly 5 3x transistor gates.
|
||||
|
|
@ -26,6 +26,9 @@ def check(check, str):
|
|||
log("ERROR: file {0}: line {1}: {2}\n".format(
|
||||
os.path.basename(filename), line_number, str))
|
||||
|
||||
if globals.OPTS.debug_level > 0:
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
assert 0
|
||||
|
||||
|
||||
|
|
@ -37,6 +40,9 @@ def error(str, return_value=0):
|
|||
log("ERROR: file {0}: line {1}: {2}\n".format(
|
||||
os.path.basename(filename), line_number, str))
|
||||
|
||||
if globals.OPTS.debug_level > 0 and return_value != 0:
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
assert return_value == 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ import debug
|
|||
from drc_value import *
|
||||
from drc_lut import *
|
||||
|
||||
class design_rules():
|
||||
"""
|
||||
This is a class that implements the design rules structures.
|
||||
|
||||
class design_rules(dict):
|
||||
"""
|
||||
This is a class that implements the design rules structures.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
self.tech_name = name
|
||||
|
|
@ -43,6 +44,22 @@ class design_rules():
|
|||
else:
|
||||
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)
|
||||
|
||||
|
||||
|
||||
def keys(self):
|
||||
return self.rules.keys()
|
||||
|
||||
def add_layer(self, name, width, spacing, area=0):
|
||||
# Minimum width
|
||||
self.add("minwidth_{}".format(name), width)
|
||||
# Minimum spacing (could be a table too)
|
||||
self.add("{0}_to_{0}".format(name), spacing)
|
||||
# Minimum area
|
||||
self.add("minarea_{}".format(name), area)
|
||||
|
||||
def add_enclosure(self, name, layer, enclosure, extension=None):
|
||||
self.add("{0}_enclose_{1}".format(name, layer), enclosure)
|
||||
# Reserved for asymmetric enclosures
|
||||
if extension:
|
||||
self.add("{0}_extend_{1}".format(name, layer), extension)
|
||||
else:
|
||||
self.add("{0}_extend_{1}".format(name, layer), enclosure)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
#
|
||||
import debug
|
||||
|
||||
|
||||
class drc_lut():
|
||||
"""
|
||||
Implement a lookup table of rules.
|
||||
"""
|
||||
Implement a lookup table of rules.
|
||||
Each element is a tuple with the last value being the rule.
|
||||
It searches through backwards until all of the key values are
|
||||
met and returns the rule value.
|
||||
|
|
@ -31,7 +32,6 @@ class drc_lut():
|
|||
for table_key in sorted(self.table.keys(), reverse=True):
|
||||
if self.match(key, table_key):
|
||||
return self.table[table_key]
|
||||
|
||||
|
||||
def match(self, key1, key2):
|
||||
"""
|
||||
|
|
@ -39,8 +39,8 @@ class drc_lut():
|
|||
(i.e. return false if key1<key2 for any pair.)
|
||||
"""
|
||||
# If any one pair is less than, return False
|
||||
debug.check(len(key1)==len(key2),"Comparing invalid key lengths.")
|
||||
for k1,k2 in zip(key1,key2):
|
||||
debug.check(len(key1) == len(key2), "Comparing invalid key lengths.")
|
||||
for k1, k2 in zip(key1, key2):
|
||||
if k1 < k2:
|
||||
return False
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
|
||||
|
||||
class drc_value():
|
||||
"""
|
||||
"""
|
||||
A single DRC value.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ word_size = 32
|
|||
num_words = 128
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
|
@ -9,6 +10,3 @@ temperatures = [25]
|
|||
output_path = "temp"
|
||||
output_name = "sram_{0}_{1}_{2}".format(word_size, num_words, tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ num_r_ports = 1
|
|||
num_w_ports = 0
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
|
@ -17,7 +18,3 @@ output_path = "temp"
|
|||
output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,
|
||||
num_words,
|
||||
tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
|
@ -18,6 +19,3 @@ output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size,
|
|||
num_words,
|
||||
tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ word_size = 2
|
|||
num_words = 16
|
||||
|
||||
tech_name = "freepdk45"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [1.0]
|
||||
temperatures = [25]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ word_size = 2
|
|||
num_words = 16
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
|
@ -14,6 +15,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
|
|||
num_words,
|
||||
tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ word_size = 64
|
|||
num_words = 1024
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [ 5.0 ]
|
||||
temperatures = [ 25 ]
|
||||
|
|
@ -10,7 +11,3 @@ output_path = "temp"
|
|||
output_name = "sram_{0}_{1}_{2}".format(word_size,
|
||||
num_words,
|
||||
tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ word_size = 16
|
|||
num_words = 256
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
nominal_corners_only = False
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [3.3]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
||||
output_path = "temp"
|
||||
|
|
@ -11,6 +12,3 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,
|
|||
num_words,
|
||||
tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ class Gds2reader:
|
|||
self.fileHandle = None
|
||||
self.layoutObject = layoutObject
|
||||
self.debugToTerminal=debugToTerminal
|
||||
|
||||
|
||||
#do we dump debug data to the screen
|
||||
|
||||
|
||||
def print64AsBinary(self,number):
|
||||
for index in range(0,64):
|
||||
print((number>>(63-index))&0x1,eol='')
|
||||
print("\n")
|
||||
|
||||
|
||||
def stripNonASCII(self,bytestring):
|
||||
string = bytestring.decode('utf-8')
|
||||
return string
|
||||
|
|
@ -29,20 +29,20 @@ class Gds2reader:
|
|||
#(1)sign (7)exponent (56)mantissa
|
||||
#exponent is excess 64, mantissa has no implied 1
|
||||
#a normal IEEE double is like this:
|
||||
#(1)sign (11)exponent (52)mantissa
|
||||
#(1)sign (11)exponent (52)mantissa
|
||||
data = struct.unpack('>q',ibmData)[0]
|
||||
sign = (data >> 63)&0x01
|
||||
exponent = (data >> 56) & 0x7f
|
||||
mantissa = data<<8 #chop off sign and exponent
|
||||
|
||||
|
||||
if mantissa == 0:
|
||||
newFloat = 0.0
|
||||
else:
|
||||
else:
|
||||
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
||||
#re normalize
|
||||
while mantissa & 0x8000000000000000 == 0:
|
||||
while mantissa & 0x8000000000000000 == 0:
|
||||
mantissa<<=1
|
||||
exponent-=1
|
||||
exponent-=1
|
||||
mantissa<<=1 #remove the assumed high bit
|
||||
exponent-=1
|
||||
#check for underflow error -- should handle these properly!
|
||||
|
|
@ -56,7 +56,7 @@ class Gds2reader:
|
|||
#convert back to double
|
||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||
return newFloat
|
||||
|
||||
|
||||
def ieeeFloatCheck(self,aFloat):
|
||||
asciiDouble = struct.pack('>d',aFloat)
|
||||
data = struct.unpack('>q',asciiDouble)[0]
|
||||
|
|
@ -70,12 +70,12 @@ class Gds2reader:
|
|||
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||
print("Check:"+str(newFloat))
|
||||
|
||||
|
||||
def readNextRecord(self):
|
||||
global offset
|
||||
recordLengthAscii = self.fileHandle.read(2) #first 2 bytes tell us the length of the record
|
||||
if len(recordLengthAscii)==0:
|
||||
return
|
||||
return
|
||||
recordLength = struct.unpack(">h",recordLengthAscii) #gives us a tuple with a short int inside
|
||||
offset_int = int(recordLength[0]) # extract length
|
||||
offset += offset_int # count offset
|
||||
|
|
@ -96,20 +96,20 @@ class Gds2reader:
|
|||
else:
|
||||
print("Invalid GDSII Header")
|
||||
return -1
|
||||
|
||||
|
||||
#read records until we hit the UNITS section... this is the last part of the header
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
idBits = record[0:2]
|
||||
## Modified Date
|
||||
if idBits==b'\x01\x02' and len(record)==26:
|
||||
modYear = struct.unpack(">h",record[2:4])[0]
|
||||
modYear = struct.unpack(">h",record[2:4])[0]
|
||||
modMonth = struct.unpack(">h",record[4:6])[0]
|
||||
modDay = struct.unpack(">h",record[6:8])[0]
|
||||
modHour = struct.unpack(">h",record[8:10])[0]
|
||||
modMinute = struct.unpack(">h",record[10:12])[0]
|
||||
modSecond = struct.unpack(">h",record[12:14])[0]
|
||||
lastAccessYear = struct.unpack(">h",record[14:16])[0]
|
||||
lastAccessYear = struct.unpack(">h",record[14:16])[0]
|
||||
lastAccessMonth = struct.unpack(">h",record[16:18])[0]
|
||||
lastAccessDay = struct.unpack(">h",record[18:20])[0]
|
||||
lastAccessHour = struct.unpack(">h",record[20:22])[0]
|
||||
|
|
@ -164,17 +164,19 @@ class Gds2reader:
|
|||
print("Mask: "+mask)
|
||||
elif(idBits==b'\x03\x05'): #this is also wrong b/c python doesn't natively have an 8 byte float
|
||||
userUnits=self.ieeeDoubleFromIbmData(record[2:10])
|
||||
dbUnits=self.ieeeDoubleFromIbmData
|
||||
dbUnits=self.ieeeDoubleFromIbmData(record[10:18])
|
||||
self.layoutObject.info["units"] = (userUnits,dbUnits)
|
||||
if(self.debugToTerminal==1):
|
||||
print("Units: 1 user unit="+str(userUnits)+" database units, 1 database unit="+str(dbUnits)+" meters.")
|
||||
break;
|
||||
if(self.debugToTerminal==1):
|
||||
if(self.debugToTerminal==1):
|
||||
print("End of GDSII Header Found")
|
||||
return 1
|
||||
|
||||
|
||||
def readBoundary(self):
|
||||
##reads in a boundary type structure = a filled polygon
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginBoundary")
|
||||
thisBoundary=GdsBoundary()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -196,16 +198,11 @@ class Gds2reader:
|
|||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||
elif(idBits==b'\x16\x02'): #Purpose
|
||||
elif(idBits==b'\x0E\x02'): #Purpose DATATYPE
|
||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||
thisBoundary.purposeLayer=purposeLayer
|
||||
thisBoundary.purposeLayer=purposeLayer
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||
elif(idBits==b'\x0E\x02'): #DataType
|
||||
dataType = struct.unpack(">h",record[2:4])[0]
|
||||
thisBoundary.dataType=dataType
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tData Type: "+str(dataType))
|
||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||
numDataPoints = len(record)-2 #packed as XY coordinates 4 bytes each
|
||||
thisBoundary.coordinates=[]
|
||||
|
|
@ -216,10 +213,15 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndBoundary")
|
||||
break;
|
||||
return thisBoundary
|
||||
|
||||
|
||||
def readPath(self): #reads in a path structure
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginPath")
|
||||
|
||||
thisPath=GdsPath()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -243,7 +245,7 @@ class Gds2reader:
|
|||
print("\t\t\tDrawing Layer: "+str(drawingLayer))
|
||||
elif(idBits==b'\x16\x02'): #Purpose
|
||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||
thisPath.purposeLayer=purposeLayer
|
||||
thisPath.purposeLayer=purposeLayer
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||
elif(idBits==b'\x21\x02'): #Path type
|
||||
|
|
@ -251,6 +253,11 @@ class Gds2reader:
|
|||
thisPath.pathType=pathType
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tPath Type: "+str(pathType))
|
||||
elif(idBits==b'\x0E\x02'): #Data type
|
||||
dataType = struct.unpack(">h",record[2:4])[0]
|
||||
thisPath.dataType=dataType
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tData Type: "+str(dataType))
|
||||
elif(idBits==b'\x0F\x03'): #Path width
|
||||
pathWidth = struct.unpack(">i",record[2:6])[0]
|
||||
thisPath.pathWidth=pathWidth
|
||||
|
|
@ -266,10 +273,15 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndPath")
|
||||
break;
|
||||
return thisPath
|
||||
|
||||
|
||||
def readSref(self): #reads in a reference to another structure
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginSref")
|
||||
|
||||
thisSref=GdsSref()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -306,7 +318,7 @@ class Gds2reader:
|
|||
print("\t\t\tMagnification:"+str(magFactor))
|
||||
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
||||
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
||||
thisSref.rotateAngle=rotateAngle
|
||||
thisSref.rotateAngle=rotateAngle
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||
|
|
@ -317,10 +329,15 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndSref")
|
||||
break;
|
||||
return thisSref
|
||||
|
||||
|
||||
def readAref(self): #an array of references
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginAref")
|
||||
|
||||
thisAref = GdsAref()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -357,7 +374,7 @@ class Gds2reader:
|
|||
print("\t\t\tMagnification:"+str(magFactor))
|
||||
elif(idBits==b'\x1C\x05'): #Rotate Angle
|
||||
rotateAngle=self.ieeeDoubleFromIbmData(record[2:10])
|
||||
thisAref.rotateAngle=rotateAngle
|
||||
thisAref.rotateAngle=rotateAngle
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tRotate Angle (CCW):"+str(rotateAngle))
|
||||
elif(idBits==b'\x10\x03'): #XY Data Points
|
||||
|
|
@ -372,11 +389,15 @@ class Gds2reader:
|
|||
print("\t\t\t\tArray Width: "+str(rightMostX-topLeftX))
|
||||
print("\t\t\t\tArray Height: "+str(topLeftY-bottomMostY))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndAref")
|
||||
break;
|
||||
return thisAref
|
||||
|
||||
|
||||
def readText(self):
|
||||
##reads in a text structure
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginText")
|
||||
|
||||
thisText=GdsText()
|
||||
while 1:
|
||||
record = self.readNextRecord()
|
||||
|
|
@ -398,9 +419,9 @@ class Gds2reader:
|
|||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||
elif(idBits==b'\x16\x02'): #Purpose
|
||||
elif(idBits==b'\x16\x02'): #Purpose TEXTTYPE
|
||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||
thisText.purposeLayer=purposeLayer
|
||||
thisText.purposeLayer=purposeLayer
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||
elif(idBits==b'\x1A\x01'): #Transformation
|
||||
|
|
@ -472,10 +493,15 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tText String: "+textString)
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndText")
|
||||
break;
|
||||
return thisText
|
||||
|
||||
|
||||
def readNode(self):
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginNode")
|
||||
|
||||
##reads in a node type structure = an electrical net
|
||||
thisNode = GdsNode()
|
||||
while 1:
|
||||
|
|
@ -513,10 +539,15 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndNode")
|
||||
break;
|
||||
return thisNode
|
||||
|
||||
|
||||
def readBox(self):
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tBeginBox")
|
||||
|
||||
##reads in a gds BOX structure
|
||||
thisBox = GdsBox()
|
||||
while 1:
|
||||
|
|
@ -539,9 +570,9 @@ class Gds2reader:
|
|||
self.layoutObject.layerNumbersInUse += [drawingLayer]
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tDrawing Layer: "+str(drawingLayer))
|
||||
elif(idBits==b'\x16\x02'): #Purpose
|
||||
elif(idBits==b'\x16\x02'): #Purpose TEXTYPE
|
||||
purposeLayer = struct.unpack(">h",record[2:4])[0]
|
||||
thisBox.purposeLayer=purposeLayer
|
||||
thisBox.purposeLayer=purposeLayer
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\tPurpose Layer: "+str(purposeLayer))
|
||||
elif(idBits==b'\x2D\x00'): #Box
|
||||
|
|
@ -559,15 +590,18 @@ class Gds2reader:
|
|||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tXY Point: "+str(x)+","+str(y))
|
||||
elif(idBits==b'\x11\x00'): #End Of Element
|
||||
if(self.debugToTerminal==1):
|
||||
print("\t\t\tEndBox")
|
||||
break;
|
||||
return thisBox
|
||||
|
||||
|
||||
def readNextStructure(self):
|
||||
thisStructure = GdsStructure()
|
||||
thisStructure = GdsStructure()
|
||||
record = self.readNextRecord()
|
||||
idBits = record[0:2]
|
||||
# Begin structure
|
||||
if(idBits==b'\x05\x02' and len(record)==26):
|
||||
createYear = struct.unpack(">h",record[2:4])[0]
|
||||
createYear = struct.unpack(">h",record[2:4])[0]
|
||||
createMonth = struct.unpack(">h",record[4:6])[0]
|
||||
createDay = struct.unpack(">h",record[6:8])[0]
|
||||
createHour = struct.unpack(">h",record[8:10])[0]
|
||||
|
|
@ -581,6 +615,10 @@ class Gds2reader:
|
|||
modSecond = struct.unpack(">h",record[24:26])[0]
|
||||
thisStructure.createDate=(createYear,createMonth,createDay,createHour,createMinute,createSecond)
|
||||
thisStructure.modDate=(modYear,modMonth,modDay,modHour,modMinute,modSecond)
|
||||
if(self.debugToTerminal==1):
|
||||
print("Date Created:"+str(createYear)+","+str(createMonth)+","+str(createDay)+\
|
||||
","+str(createHour)+","+str(createMinute)+","+str(createSecond))
|
||||
print("Date Modified:"+str(modYear)+","+str(modMonth)+","+str(modDay)+","+str(modHour)+","+str(modMinute)+","+str(modSecond))
|
||||
else:
|
||||
#means we have hit the last structure, so return the record
|
||||
#to whoever called us to do something with it
|
||||
|
|
@ -590,7 +628,7 @@ class Gds2reader:
|
|||
idBits = record[0:2]
|
||||
if idBits==b'\x07\x00': break; #we've reached the end of the structure
|
||||
elif(idBits==b'\x06\x06'):
|
||||
structName = self.stripNonASCII(record[2::])
|
||||
structName = self.stripNonASCII(record[2::])
|
||||
thisStructure.name = structName
|
||||
if(self.debugToTerminal==1):
|
||||
print("\tStructure Name: "+structName)
|
||||
|
|
@ -608,11 +646,11 @@ class Gds2reader:
|
|||
thisStructure.nodes+=[self.readNode()]
|
||||
elif(idBits==b'\x2E\x02'):
|
||||
thisStructure.boxes+=[self.readBox()]
|
||||
if(self.debugToTerminal==1):
|
||||
if(self.debugToTerminal==1):
|
||||
print("\tEnd of Structure.")
|
||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||
return 1
|
||||
|
||||
|
||||
def readGds2(self):
|
||||
if(self.readHeader()): #did the header read ok?
|
||||
record = self.readNextStructure()
|
||||
|
|
@ -629,7 +667,7 @@ class Gds2reader:
|
|||
print("There was an error reading the structure list.")
|
||||
else:
|
||||
print("There was an error parsing the GDS header. Aborting...")
|
||||
|
||||
|
||||
def loadFromFile(self, fileName):
|
||||
self.fileHandle = open(fileName,"rb")
|
||||
self.readGds2()
|
||||
|
|
@ -657,11 +695,11 @@ class Gds2reader:
|
|||
|
||||
def findStruct_readNextStruct(self,findStructName):
|
||||
self.debugToTerminal=0
|
||||
thisStructure = GdsStructure()
|
||||
thisStructure = GdsStructure()
|
||||
record = self.readNextRecord()
|
||||
idBits = record[0:2]
|
||||
if(idBits==('\x05','\x02') and len(record)==26):
|
||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
||||
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
||||
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
||||
|
|
@ -686,10 +724,6 @@ class Gds2reader:
|
|||
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
||||
elif(idBits==('\x06','\x06')):
|
||||
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
||||
# print(''.[x for x in structName if ord(x) < 128])
|
||||
# stripped = (c for c in structName if 0 < ord(c) < 127)
|
||||
# structName = "".join(stripped)
|
||||
# print(self.stripNonASCII(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
|
||||
thisStructure.name = structName
|
||||
if(findStructName==thisStructure.name):
|
||||
wantedStruct=1
|
||||
|
|
@ -709,7 +743,7 @@ class Gds2reader:
|
|||
thisStructure.nodes+=[self.readNode()]
|
||||
elif(idBits==('\x2E','\x02')):
|
||||
thisStructure.boxes+=[self.readBox()]
|
||||
if(self.debugToTerminal==1):
|
||||
if(self.debugToTerminal==1):
|
||||
print("\tEnd of Structure.")
|
||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||
if(wantedStruct == 0):
|
||||
|
|
@ -737,11 +771,11 @@ class Gds2reader:
|
|||
|
||||
def findLabel_readNextStruct(self,findLabelName):
|
||||
self.debugToTerminal=0
|
||||
thisStructure = GdsStructure()
|
||||
thisStructure = GdsStructure()
|
||||
record = self.readNextRecord()
|
||||
idBits = record[0:2]
|
||||
if(idBits==('\x05','\x02') and len(record)==26):
|
||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||
createYear = struct.unpack(">h",record[2]+record[3])[0]
|
||||
createMonth = struct.unpack(">h",record[4]+record[5])[0]
|
||||
createDay = struct.unpack(">h",record[6]+record[7])[0]
|
||||
createHour = struct.unpack(">h",record[8]+record[9])[0]
|
||||
|
|
@ -767,10 +801,6 @@ class Gds2reader:
|
|||
if idBits==('\x07','\x00'): break; #we've reached the end of the structure
|
||||
elif(idBits==('\x06','\x06')):
|
||||
structName = self.stripNonASCII(record[2::]) #(record[2:1] + record[1::]).rstrip()
|
||||
# print(''.[x for x in structName if ord(x) < 128])
|
||||
# stripped = (c for c in structName if 0 < ord(c) < 127)
|
||||
# structName = "".join(stripped)
|
||||
# print(self.stripNonASCIIx(structName)) ##FIXME: trimming by Tom g. ##could be an issue here with string trimming!
|
||||
thisStructure.name = structName
|
||||
if(self.debugToTerminal==1):
|
||||
print("\tStructure Name: "+structName)
|
||||
|
|
@ -795,7 +825,7 @@ class Gds2reader:
|
|||
thisStructure.nodes+=[self.readNode()]
|
||||
elif(idBits==('\x2E','\x02')):
|
||||
thisStructure.boxes+=[self.readBox()]
|
||||
if(self.debugToTerminal==1):
|
||||
if(self.debugToTerminal==1):
|
||||
print("\tEnd of Structure.")
|
||||
self.layoutObject.structures[structName]=thisStructure #add this structure to the layout object
|
||||
if(wantedLabel == 0):
|
||||
|
|
@ -803,4 +833,3 @@ class Gds2reader:
|
|||
else:
|
||||
#print("\tDone with collectting bound. Return")
|
||||
return [0,wantedtexts]
|
||||
|
||||
|
|
|
|||
|
|
@ -5,37 +5,37 @@ from .gdsPrimitives import *
|
|||
class Gds2writer:
|
||||
"""Class to take a populated layout class and write it to a file in GDSII format"""
|
||||
## Based on info from http://www.rulabinsky.com/cavd/text/chapc.html
|
||||
|
||||
|
||||
def __init__(self,layoutObject):
|
||||
self.fileHandle = 0
|
||||
self.layoutObject = layoutObject
|
||||
self.debugToTerminal=0 #do we dump debug data to the screen
|
||||
|
||||
|
||||
def print64AsBinary(self,number):
|
||||
#debugging method for binary inspection
|
||||
for index in range(0,64):
|
||||
print((number>>(63-index))&0x1,eol='')
|
||||
print("\n")
|
||||
|
||||
|
||||
def ieeeDoubleFromIbmData(self,ibmData):
|
||||
#the GDS double is in IBM 370 format like this:
|
||||
#(1)sign (7)exponent (56)mantissa
|
||||
#exponent is excess 64, mantissa has no implied 1
|
||||
#a normal IEEE double is like this:
|
||||
#(1)sign (11)exponent (52)mantissa
|
||||
#(1)sign (11)exponent (52)mantissa
|
||||
data = struct.unpack('>q',ibmData)[0]
|
||||
sign = (data >> 63)&0x01
|
||||
exponent = (data >> 56) & 0x7f
|
||||
mantissa = data<<8 #chop off sign and exponent
|
||||
|
||||
|
||||
if mantissa == 0:
|
||||
newFloat = 0.0
|
||||
else:
|
||||
exponent = ((exponent-64)*4)+1023 #convert to double exponent
|
||||
#re normalize
|
||||
while mantissa & 0x8000000000000000 == 0:
|
||||
while mantissa & 0x8000000000000000 == 0:
|
||||
mantissa<<=1
|
||||
exponent-=1
|
||||
exponent-=1
|
||||
mantissa<<=1 #remove the assumed high bit
|
||||
exponent-=1
|
||||
#check for underflow error -- should handle these properly!
|
||||
|
|
@ -49,12 +49,12 @@ class Gds2writer:
|
|||
#convert back to double
|
||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||
return newFloat
|
||||
|
||||
|
||||
def ibmDataFromIeeeDouble(self,ieeeDouble):
|
||||
asciiDouble = struct.pack('>d',ieeeDouble)
|
||||
data = struct.unpack('>q',asciiDouble)[0]
|
||||
sign = (data >> 63) & 0x01
|
||||
exponent = ((data >> 52) & 0x7ff)-1023
|
||||
exponent = ((data >> 52) & 0x7ff)-1023
|
||||
mantissa = data << 12 #chop off sign and exponent
|
||||
if(ieeeDouble == 0):
|
||||
mantissa = 0
|
||||
|
|
@ -70,14 +70,14 @@ class Gds2writer:
|
|||
for index in range (0,-exponent&3):
|
||||
mantissa >>= 1
|
||||
mantissa = mantissa & 0x7fffffffffffffff
|
||||
|
||||
exponent = (exponent+3) >> 2
|
||||
|
||||
exponent = (exponent+3) >> 2
|
||||
exponent+=64
|
||||
|
||||
newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff)
|
||||
asciiDouble = struct.pack('>q',newFloat)
|
||||
|
||||
newFloat =(sign<<63)|(exponent<<56)|((mantissa>>8)&0xffffffffffffff)
|
||||
asciiDouble = struct.pack('>q',newFloat)
|
||||
return asciiDouble
|
||||
|
||||
|
||||
def ieeeFloatCheck(self,aFloat):
|
||||
#debugging method for float construction
|
||||
asciiDouble = struct.pack('>d',aFloat)
|
||||
|
|
@ -90,7 +90,7 @@ class Gds2writer:
|
|||
asciiDouble = struct.pack('>q',(sign<<63)|(exponent+1023<<52)|(mantissa>>12))
|
||||
newFloat = struct.unpack('>d',asciiDouble)[0]
|
||||
print("Check:"+str(newFloat))
|
||||
|
||||
|
||||
def writeRecord(self,record):
|
||||
recordLength = len(record)+2 #make sure to include this in the length
|
||||
recordLengthAscii=struct.pack(">h",recordLength)
|
||||
|
|
@ -127,7 +127,7 @@ class Gds2writer:
|
|||
libraryName = self.layoutObject.info["libraryName"].encode() + "\0"
|
||||
else:
|
||||
libraryName = self.layoutObject.info["libraryName"].encode()
|
||||
self.writeRecord(idBits+libraryName)
|
||||
self.writeRecord(idBits+libraryName)
|
||||
## reference libraries
|
||||
if("referenceLibraries" in self.layoutObject.info):
|
||||
idBits=b'\x1F\x06'
|
||||
|
|
@ -158,11 +158,11 @@ class Gds2writer:
|
|||
mask = self.layoutObject.info["mask"]
|
||||
self.writeRecord(idBits+mask)
|
||||
if("units" in self.layoutObject.info):
|
||||
idBits=b'\x03\x05'
|
||||
idBits=b'\x03\x05'
|
||||
userUnits=self.ibmDataFromIeeeDouble(self.layoutObject.info["units"][0])
|
||||
dbUnits=self.ibmDataFromIeeeDouble((self.layoutObject.info["units"][0]*1e-6/self.layoutObject.info["units"][1])*self.layoutObject.info["units"][1])
|
||||
|
||||
#User Units are hardcoded, since the floating point implementation of gdsMill is not adequate,
|
||||
#User Units are hardcoded, since the floating point implementation of gdsMill is not adequate,
|
||||
#resulting in a different value being written in output stream. Hardcoded to sram compiler's outputed gds units.
|
||||
#db="39225c17d04dad2a"
|
||||
#uu="3e20c49ba5e353f8"
|
||||
|
|
@ -172,42 +172,40 @@ class Gds2writer:
|
|||
|
||||
#dbUnits="39225c17d04dad2a".decode("hex")
|
||||
#db=39225c17d04dad2a
|
||||
|
||||
|
||||
|
||||
|
||||
self.writeRecord(idBits+userUnits+dbUnits)
|
||||
if(self.debugToTerminal==1):
|
||||
if(self.debugToTerminal==1):
|
||||
print("writer: userUnits %s"%(userUnits.encode("hex")))
|
||||
print("writer: dbUnits %s"%(dbUnits.encode("hex")))
|
||||
#self.ieeeFloatCheck(1.3e-6)
|
||||
|
||||
|
||||
print("End of GDSII Header Written")
|
||||
return 1
|
||||
|
||||
|
||||
def writeBoundary(self,thisBoundary):
|
||||
idBits=b'\x08\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
if(thisBoundary.elementFlags!=""):
|
||||
idBits=b'\x26\x01' #ELFLAGS
|
||||
idBits=b'\x26\x01' # ELFLAGS
|
||||
elementFlags = struct.pack(">h",thisBoundary.elementFlags)
|
||||
self.writeRecord(idBits+elementFlags)
|
||||
if(thisBoundary.plex!=""):
|
||||
idBits=b'\x2F\x03' #PLEX
|
||||
idBits=b'\x2F\x03' # PLEX
|
||||
plex = struct.pack(">i",thisBoundary.plex)
|
||||
self.writeRecord(idBits+plex)
|
||||
if(thisBoundary.drawingLayer!=""):
|
||||
idBits=b'\x0D\x02' #drawig layer
|
||||
idBits=b'\x0D\x02' # drawing layer
|
||||
drawingLayer = struct.pack(">h",thisBoundary.drawingLayer)
|
||||
self.writeRecord(idBits+drawingLayer)
|
||||
if(thisBoundary.purposeLayer):
|
||||
idBits=b'\x16\x02' #purpose layer
|
||||
purposeLayer = struct.pack(">h",thisBoundary.purposeLayer)
|
||||
self.writeRecord(idBits+purposeLayer)
|
||||
if(thisBoundary.dataType!=""):
|
||||
idBits=b'\x0E\x02'#DataType
|
||||
dataType = struct.pack(">h",thisBoundary.dataType)
|
||||
if(thisBoundary.purposeLayer!=""):
|
||||
idBits=b'\x0E\x02' # DataType
|
||||
if type(thisBoundary.purposeLayer)!=int:
|
||||
import pdb; pdb.set_trace()
|
||||
dataType = struct.pack(">h",thisBoundary.purposeLayer)
|
||||
self.writeRecord(idBits+dataType)
|
||||
if(thisBoundary.coordinates!=""):
|
||||
idBits=b'\x10\x03' #XY Data Points
|
||||
idBits=b'\x10\x03' # XY Data Points
|
||||
coordinateRecord = idBits
|
||||
for coordinate in thisBoundary.coordinates:
|
||||
x=struct.pack(">i",int(coordinate[0]))
|
||||
|
|
@ -218,7 +216,7 @@ class Gds2writer:
|
|||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writePath(self,thisPath): #writes out a path structure
|
||||
idBits=b'\x09\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -238,6 +236,10 @@ class Gds2writer:
|
|||
idBits=b'\x16\x02' #purpose layer
|
||||
purposeLayer = struct.pack(">h",thisPath.purposeLayer)
|
||||
self.writeRecord(idBits+purposeLayer)
|
||||
if(thisPath.dataType is not None):
|
||||
idBits=b'\x0E\x02' #Data type
|
||||
dataType = struct.pack(">h",thisPath.dataType)
|
||||
self.writeRecord(idBits+dataType)
|
||||
if(thisPath.pathType):
|
||||
idBits=b'\x21\x02' #Path type
|
||||
pathType = struct.pack(">h",thisPath.pathType)
|
||||
|
|
@ -258,7 +260,7 @@ class Gds2writer:
|
|||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeSref(self,thisSref): #reads in a reference to another structure
|
||||
idBits=b'\x0A\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -294,7 +296,7 @@ class Gds2writer:
|
|||
magFactor=self.ibmDataFromIeeeDouble(thisSref.magFactor)
|
||||
self.writeRecord(idBits+magFactor)
|
||||
if(thisSref.rotateAngle!=""):
|
||||
idBits=b'\x1C\x05'
|
||||
idBits=b'\x1C\x05'
|
||||
rotateAngle=self.ibmDataFromIeeeDouble(thisSref.rotateAngle)
|
||||
self.writeRecord(idBits+rotateAngle)
|
||||
if(thisSref.coordinates!=""):
|
||||
|
|
@ -310,7 +312,7 @@ class Gds2writer:
|
|||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeAref(self,thisAref): #an array of references
|
||||
idBits=b'\x0B\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -346,7 +348,7 @@ class Gds2writer:
|
|||
magFactor=self.ibmDataFromIeeeDouble(thisAref.magFactor)
|
||||
self.writeRecord(idBits+magFactor)
|
||||
if(thisAref.rotateAngle!=""):
|
||||
idBits=b'\x1C\x05'
|
||||
idBits=b'\x1C\x05'
|
||||
rotateAngle=self.ibmDataFromIeeeDouble(thisAref.rotateAngle)
|
||||
self.writeRecord(idBits+rotateAngle)
|
||||
if(thisAref.coordinates):
|
||||
|
|
@ -361,7 +363,7 @@ class Gds2writer:
|
|||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeText(self,thisText):
|
||||
idBits=b'\x0C\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -377,8 +379,7 @@ class Gds2writer:
|
|||
idBits=b'\x0D\x02' #drawing layer
|
||||
drawingLayer = struct.pack(">h",thisText.drawingLayer)
|
||||
self.writeRecord(idBits+drawingLayer)
|
||||
#if(thisText.purposeLayer):
|
||||
idBits=b'\x16\x02' #purpose layer
|
||||
idBits=b'\x16\x02' #purpose layer TEXTTYPE
|
||||
purposeLayer = struct.pack(">h",thisText.purposeLayer)
|
||||
self.writeRecord(idBits+purposeLayer)
|
||||
if(thisText.transFlags != ""):
|
||||
|
|
@ -398,7 +399,7 @@ class Gds2writer:
|
|||
magFactor=self.ibmDataFromIeeeDouble(thisText.magFactor)
|
||||
self.writeRecord(idBits+magFactor)
|
||||
if(thisText.rotateAngle!=""):
|
||||
idBits=b'\x1C\x05'
|
||||
idBits=b'\x1C\x05'
|
||||
rotateAngle=self.ibmDataFromIeeeDouble(thisText.rotateAngle)
|
||||
self.writeRecord(idBits+rotateAngle)
|
||||
if(thisText.pathType !=""):
|
||||
|
|
@ -410,12 +411,12 @@ class Gds2writer:
|
|||
pathWidth = struct.pack(">i",thisText.pathWidth)
|
||||
self.writeRecord(idBits+pathWidth)
|
||||
if(thisText.presentationFlags!=""):
|
||||
idBits=b'\x1A\x01'
|
||||
idBits=b'\x1A\x01'
|
||||
font = thisText.presentationFlags[0]<<4
|
||||
verticalFlags = int(thisText.presentationFlags[1])<<2
|
||||
horizontalFlags = int(thisText.presentationFlags[2])
|
||||
presentationFlags = struct.pack(">H",font|verticalFlags|horizontalFlags)
|
||||
self.writeRecord(idBits+transFlags)
|
||||
self.writeRecord(idBits+transFlags)
|
||||
if(thisText.coordinates!=""):
|
||||
idBits=b'\x10\x03' #XY Data Points
|
||||
coordinateRecord = idBits
|
||||
|
|
@ -429,11 +430,11 @@ class Gds2writer:
|
|||
idBits=b'\x19\x06'
|
||||
textString = thisText.textString
|
||||
self.writeRecord(idBits+textString.encode())
|
||||
|
||||
|
||||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeNode(self,thisNode):
|
||||
idBits=b'\x15\x00' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -448,11 +449,11 @@ class Gds2writer:
|
|||
if(thisNode.drawingLayer!=""):
|
||||
idBits=b'\x0D\x02' #drawig layer
|
||||
drawingLayer = struct.pack(">h",thisNode.drawingLayer)
|
||||
self.writeRecord(idBits+drawingLayer)
|
||||
self.writeRecord(idBits+drawingLayer)
|
||||
if(thisNode.nodeType!=""):
|
||||
idBits=b'\x2A\x02'
|
||||
nodeType = struct.pack(">h",thisNode.nodeType)
|
||||
self.writeRecord(idBits+nodeType)
|
||||
self.writeRecord(idBits+nodeType)
|
||||
if(thisText.coordinates!=""):
|
||||
idBits=b'\x10\x03' #XY Data Points
|
||||
coordinateRecord = idBits
|
||||
|
|
@ -462,11 +463,11 @@ class Gds2writer:
|
|||
coordinateRecord+=x
|
||||
coordinateRecord+=y
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeBox(self,thisBox):
|
||||
idBits=b'\x2E\x02' #record Type
|
||||
self.writeRecord(idBits)
|
||||
|
|
@ -489,7 +490,7 @@ class Gds2writer:
|
|||
if(thisBox.boxValue!=""):
|
||||
idBits=b'\x2D\x00'
|
||||
boxValue = struct.pack(">h",thisBox.boxValue)
|
||||
self.writeRecord(idBits+boxValue)
|
||||
self.writeRecord(idBits+boxValue)
|
||||
if(thisBox.coordinates!=""):
|
||||
idBits=b'\x10\x03' #XY Data Points
|
||||
coordinateRecord = idBits
|
||||
|
|
@ -499,11 +500,11 @@ class Gds2writer:
|
|||
coordinateRecord+=x
|
||||
coordinateRecord+=y
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
idBits=b'\x11\x00' #End Of Element
|
||||
coordinateRecord = idBits
|
||||
self.writeRecord(coordinateRecord)
|
||||
|
||||
|
||||
def writeNextStructure(self,structureName):
|
||||
#first put in the structure head
|
||||
thisStructure = self.layoutObject.structures[structureName]
|
||||
|
|
@ -530,7 +531,7 @@ class Gds2writer:
|
|||
structureName = structureName + '\x00'
|
||||
self.writeRecord(idBits+structureName.encode())
|
||||
#now go through all the structure elements and write them in
|
||||
|
||||
|
||||
for boundary in thisStructure.boundaries:
|
||||
self.writeBoundary(boundary)
|
||||
for path in thisStructure.paths:
|
||||
|
|
@ -548,7 +549,7 @@ class Gds2writer:
|
|||
#put in the structure tail
|
||||
idBits=b'\x07\x00'
|
||||
self.writeRecord(idBits)
|
||||
|
||||
|
||||
def writeGds2(self):
|
||||
self.writeHeader(); #first, put the header in
|
||||
#go through each structure in the layout and write it to the file
|
||||
|
|
@ -557,7 +558,7 @@ class Gds2writer:
|
|||
#at the end, put in the END LIB record
|
||||
idBits=b'\x04\x00'
|
||||
self.writeRecord(idBits)
|
||||
|
||||
|
||||
def writeToFile(self,fileName):
|
||||
self.fileHandle = open(fileName,"wb")
|
||||
self.writeGds2()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class GdsStructure:
|
|||
#these are the primitives defined in GDS2, and we will maintain lists of them all
|
||||
self.boundaries=[]
|
||||
self.paths=[]
|
||||
self.srefs=[]
|
||||
self.srefs=[]
|
||||
self.arefs=[]
|
||||
self.texts=[]
|
||||
self.nodes=[]
|
||||
|
|
@ -21,21 +21,21 @@ class GdsBoundary:
|
|||
self.elementFlags=""
|
||||
self.plex=""
|
||||
self.drawingLayer=""
|
||||
self.purposeLayer = None
|
||||
self.dataType=""
|
||||
self.purposeLayer=0
|
||||
self.coordinates=""
|
||||
|
||||
|
||||
class GdsPath:
|
||||
"""Class represent a GDS Path Object"""
|
||||
def __init__(self):
|
||||
self.elementFlags=""
|
||||
self.plex=""
|
||||
self.drawingLayer=""
|
||||
self.purposeLayer = None
|
||||
self.purposeLayer=0
|
||||
self.pathType=""
|
||||
self.dataType=None
|
||||
self.pathWidth=""
|
||||
self.coordinates=""
|
||||
|
||||
|
||||
def equivalentBoundaryCoordinates(self):
|
||||
"""Convert the path to a set of boundary coordinates that define it"""
|
||||
halfWidth = (self.pathWidth/2)
|
||||
|
|
@ -62,7 +62,7 @@ class GdsPath:
|
|||
nextX = None;
|
||||
nextY = None;
|
||||
if lastX==None: #start of the path
|
||||
if nextX>x:#moving right
|
||||
if nextX>x:#moving right
|
||||
boundaryEquivalent+=[(x,y+halfWidth)]
|
||||
if nextX<x:#moving left
|
||||
boundaryEquivalent+=[(x,y-halfWidth)]
|
||||
|
|
@ -96,9 +96,9 @@ class GdsPath:
|
|||
boundaryEquivalent+=[(x-halfWidth,y+halfWidth)]
|
||||
if(y < lastY and x > nextX):
|
||||
boundaryEquivalent+=[(x+halfWidth,y-halfWidth)]
|
||||
|
||||
if nextX == None: #end of path, put in the last 2 points
|
||||
if lastX<x:#moving right
|
||||
|
||||
if nextX == None: #end of path, put in the last 2 points
|
||||
if lastX<x:#moving right
|
||||
boundaryEquivalent+=[(x,y+halfWidth)]
|
||||
if lastX>x:#moving left
|
||||
boundaryEquivalent+=[(x,y-halfWidth)]
|
||||
|
|
@ -140,7 +140,7 @@ class GdsText:
|
|||
self.elementFlags=""
|
||||
self.plex=""
|
||||
self.drawingLayer=""
|
||||
self.purposeLayer = None
|
||||
self.purposeLayer=0
|
||||
self.transFlags=[0,0,0]
|
||||
self.magFactor=""
|
||||
self.rotateAngle=""
|
||||
|
|
@ -149,7 +149,7 @@ class GdsText:
|
|||
self.presentationFlags=""
|
||||
self.coordinates=""
|
||||
self.textString = ""
|
||||
|
||||
|
||||
class GdsNode:
|
||||
"""Class represent a GDS Node Object"""
|
||||
def __init__(self):
|
||||
|
|
@ -158,13 +158,13 @@ class GdsNode:
|
|||
self.drawingLayer=""
|
||||
self.nodeType=""
|
||||
self.coordinates=""
|
||||
|
||||
|
||||
class GdsBox:
|
||||
"""Class represent a GDS Box Object"""
|
||||
def __init__(self):
|
||||
self.elementFlags=""
|
||||
self.plex=""
|
||||
self.drawingLayer=""
|
||||
self.purposeLayer = None
|
||||
self.purposeLayer=0
|
||||
self.boxValue=""
|
||||
self.coordinates=""
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ from .gdsPrimitives import *
|
|||
from datetime import *
|
||||
#from mpmath import matrix
|
||||
#from numpy import matrix
|
||||
from vector import vector
|
||||
import numpy as np
|
||||
#import gdsPrimitives
|
||||
import debug
|
||||
|
|
@ -19,7 +18,8 @@ class VlsiLayout:
|
|||
self.layerNumbersInUse = []
|
||||
self.debug = False
|
||||
if name:
|
||||
self.rootStructureName=name
|
||||
#take the root structure and copy it to a new structure with the new name
|
||||
self.rootStructureName=self.padText(name)
|
||||
#create the ROOT structure
|
||||
self.structures[self.rootStructureName] = GdsStructure()
|
||||
self.structures[self.rootStructureName].name = name
|
||||
|
|
@ -35,7 +35,7 @@ class VlsiLayout:
|
|||
modDate.hour,
|
||||
modDate.minute,
|
||||
modDate.second)
|
||||
|
||||
|
||||
self.info = dict() #information gathered from the GDSII header
|
||||
self.info['units']=self.units
|
||||
self.info['dates']=(modDate.year,
|
||||
|
|
@ -52,12 +52,12 @@ class VlsiLayout:
|
|||
modDate.second)
|
||||
self.info['libraryName']=libraryName
|
||||
self.info['gdsVersion']=gdsVersion
|
||||
|
||||
|
||||
self.xyTree = [] #This will contain a list of all structure names
|
||||
#expanded to include srefs / arefs separately.
|
||||
#each structure will have an X,Y,offset, and rotate associated
|
||||
#with it. Populate via traverseTheHierarchy method.
|
||||
|
||||
|
||||
#temp variables used in delegate functions
|
||||
self.tempCoordinates=None
|
||||
self.tempPassFail = True
|
||||
|
|
@ -73,22 +73,18 @@ class VlsiLayout:
|
|||
if(rotateAngle):
|
||||
angle = math.radians(float(rotateAngle))
|
||||
|
||||
coordinatesRotate = [] #this will hold the rotated values
|
||||
coordinatesRotate = [] #this will hold the rotated values
|
||||
for coordinate in coordinatesToRotate:
|
||||
# This is the CCW rotation matrix
|
||||
newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle)
|
||||
newY = coordinate[0]*math.sin(angle) + coordinate[1]*math.cos(angle)
|
||||
coordinatesRotate.extend((newX,newY))
|
||||
return coordinatesRotate
|
||||
|
||||
|
||||
def rename(self,newName):
|
||||
#make sure the newName is a multiple of 2 characters
|
||||
if(len(newName)%2 == 1):
|
||||
#pad with a zero
|
||||
newName = newName + '\x00'
|
||||
#take the root structure and copy it to a new structure with the new name
|
||||
self.structures[newName] = self.structures[self.rootStructureName]
|
||||
self.structures[newName].name = newName
|
||||
self.structures[newName].name = self.padText(newName)
|
||||
#and delete the old root
|
||||
del self.structures[self.rootStructureName]
|
||||
self.rootStructureName = newName
|
||||
|
|
@ -133,8 +129,8 @@ class VlsiLayout:
|
|||
modDate.hour,
|
||||
modDate.minute,
|
||||
modDate.second)
|
||||
|
||||
|
||||
|
||||
|
||||
#repopulate the 2d map so drawing occurs correctly
|
||||
self.prepareForWrite()
|
||||
|
||||
|
|
@ -159,14 +155,15 @@ class VlsiLayout:
|
|||
debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames)))
|
||||
self.rootStructureName = structureNames[0]
|
||||
|
||||
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
|
||||
|
||||
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
|
||||
transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)):
|
||||
#since this is a recursive function, must deal with the default
|
||||
#parameters explicitly
|
||||
#parameters explicitly
|
||||
if startingStructureName == None:
|
||||
startingStructureName = self.rootStructureName
|
||||
|
||||
#set up the rotation matrix
|
||||
startingStructureName = self.rootStructureName
|
||||
|
||||
#set up the rotation matrix
|
||||
if(rotateAngle == None or rotateAngle == ""):
|
||||
angle = 0
|
||||
else:
|
||||
|
|
@ -193,40 +190,44 @@ class VlsiLayout:
|
|||
delegateFunction(startingStructureName, transformPath)
|
||||
#starting with a particular structure, we will recursively traverse the tree
|
||||
#********might have to set the recursion level deeper for big layouts!
|
||||
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
|
||||
#if so, go through each and call this function again
|
||||
#if not, return back to the caller (caller can be this function)
|
||||
for sref in self.structures[startingStructureName].srefs:
|
||||
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
||||
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
||||
delegateFunction = delegateFunction,
|
||||
transformPath = transformPath,
|
||||
rotateAngle = sref.rotateAngle,
|
||||
transFlags = sref.transFlags,
|
||||
coordinates = sref.coordinates)
|
||||
try:
|
||||
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
|
||||
#if so, go through each and call this function again
|
||||
#if not, return back to the caller (caller can be this function)
|
||||
for sref in self.structures[startingStructureName].srefs:
|
||||
#here, we are going to modify the sref coordinates based on the parent objects rotation
|
||||
self.traverseTheHierarchy(startingStructureName = sref.sName,
|
||||
delegateFunction = delegateFunction,
|
||||
transformPath = transformPath,
|
||||
rotateAngle = sref.rotateAngle,
|
||||
transFlags = sref.transFlags,
|
||||
coordinates = sref.coordinates)
|
||||
except KeyError:
|
||||
debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1)
|
||||
|
||||
#MUST HANDLE AREFs HERE AS WELL
|
||||
#when we return, drop the last transform from the transformPath
|
||||
del transformPath[-1]
|
||||
return
|
||||
|
||||
|
||||
def initialize(self):
|
||||
self.deduceHierarchy()
|
||||
#self.traverseTheHierarchy()
|
||||
# self.traverseTheHierarchy()
|
||||
self.populateCoordinateMap()
|
||||
|
||||
for layerNumber in self.layerNumbersInUse:
|
||||
self.processLabelPins(layerNumber)
|
||||
|
||||
|
||||
self.processLabelPins((layerNumber, None))
|
||||
|
||||
|
||||
def populateCoordinateMap(self):
|
||||
def addToXyTree(startingStructureName = None,transformPath = None):
|
||||
uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors
|
||||
vVector = np.array([[0.0],[1.0],[0.0]])
|
||||
origin = np.array([[0.0],[0.0],[1.0]]) #and an origin (Z component is 1.0 to indicate position instead of vector)
|
||||
#make a copy of all the transforms and reverse it
|
||||
#make a copy of all the transforms and reverse it
|
||||
reverseTransformPath = transformPath[:]
|
||||
if len(reverseTransformPath) > 1:
|
||||
reverseTransformPath.reverse()
|
||||
reverseTransformPath.reverse()
|
||||
#now go through each transform and apply them to our basis and origin in succession
|
||||
for transform in reverseTransformPath:
|
||||
origin = np.dot(transform[0], origin) #rotate
|
||||
|
|
@ -236,28 +237,31 @@ class VlsiLayout:
|
|||
uVector = np.dot(transform[1], uVector) #scale
|
||||
vVector = np.dot(transform[1], vVector) #scale
|
||||
origin = np.dot(transform[2], origin) #translate
|
||||
#we don't need to do a translation on the basis vectors
|
||||
#we don't need to do a translation on the basis vectors
|
||||
#uVector = transform[2] * uVector #translate
|
||||
#vVector = transform[2] * vVector #translate
|
||||
#populate the xyTree with each structureName and coordinate space
|
||||
self.xyTree.append((startingStructureName,origin,uVector,vVector))
|
||||
self.traverseTheHierarchy(delegateFunction = addToXyTree)
|
||||
|
||||
def microns(self,userUnits):
|
||||
|
||||
def microns(self, userUnits):
|
||||
"""Utility function to convert user units to microns"""
|
||||
userUnit = self.units[1]/self.units[0]
|
||||
userUnitsPerMicron = userUnit / (userunit)
|
||||
userUnitsPerMicron = userUnit / userunit
|
||||
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
||||
return userUnits / layoutUnitsPerMicron
|
||||
|
||||
def userUnits(self,microns):
|
||||
|
||||
def userUnits(self, microns):
|
||||
"""Utility function to convert microns to user units"""
|
||||
userUnit = self.units[1]/self.units[0]
|
||||
#userUnitsPerMicron = userUnit / 1e-6
|
||||
# userUnitsPerMicron = userUnit / 1e-6
|
||||
userUnitsPerMicron = userUnit / (userUnit)
|
||||
layoutUnitsPerMicron = userUnitsPerMicron / self.units[0]
|
||||
#print("userUnit:",userUnit,"userUnitsPerMicron",userUnitsPerMicron,"layoutUnitsPerMicron",layoutUnitsPerMicron,[microns,microns*layoutUnitsPerMicron])
|
||||
return round(microns*layoutUnitsPerMicron,0)
|
||||
# print("userUnit:",userUnit,
|
||||
# "userUnitsPerMicron",userUnitsPerMicron,
|
||||
# "layoutUnitsPerMicron",layoutUnitsPerMicron,
|
||||
# [microns,microns*layoutUnitsPerMicron])
|
||||
return round(microns*layoutUnitsPerMicron, 0)
|
||||
|
||||
def changeRoot(self,newRoot, create=False):
|
||||
"""
|
||||
|
|
@ -266,7 +270,7 @@ class VlsiLayout:
|
|||
|
||||
if self.debug:
|
||||
debug.info(0,"DEBUG: GdsMill vlsiLayout: changeRoot: %s "%newRoot)
|
||||
|
||||
|
||||
# Determine if newRoot exists
|
||||
# layoutToAdd (default) or nameOfLayout
|
||||
if (newRoot == 0 | ((newRoot not in self.structures) & ~create)):
|
||||
|
|
@ -278,19 +282,19 @@ class VlsiLayout:
|
|||
self.rootStructureName = newRoot
|
||||
|
||||
|
||||
|
||||
|
||||
def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None):
|
||||
"""
|
||||
Method to insert one layout into another at a particular offset.
|
||||
"""
|
||||
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
||||
if self.debug:
|
||||
if self.debug:
|
||||
debug.info(0,"DEBUG: GdsMill vlsiLayout: addInstance: type {0}, nameOfLayout {1}".format(type(layoutToAdd),nameOfLayout))
|
||||
debug.info(0,"DEBUG: name={0} offset={1} mirror={2} rotate={3}".format(layoutToAdd.rootStructureName,offsetInMicrons, mirror, rotate))
|
||||
|
||||
|
||||
|
||||
# Determine if we are instantiating the root design of
|
||||
# Determine if we are instantiating the root design of
|
||||
# layoutToAdd (default) or nameOfLayout
|
||||
if nameOfLayout == 0:
|
||||
StructureFound = True
|
||||
|
|
@ -299,7 +303,7 @@ class VlsiLayout:
|
|||
StructureName = nameOfLayout #layoutToAdd
|
||||
StructureFound = False
|
||||
for structure in layoutToAdd.structures:
|
||||
if StructureName in structure:
|
||||
if StructureName in structure:
|
||||
if self.debug:
|
||||
debug.info(1,"DEBUG: Structure %s Found"%StructureName)
|
||||
StructureFound = True
|
||||
|
|
@ -307,7 +311,7 @@ class VlsiLayout:
|
|||
debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName))
|
||||
|
||||
|
||||
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
||||
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
||||
# otherwise, if it is a text name of an internal structure, use it.
|
||||
|
||||
if layoutToAdd != self:
|
||||
|
|
@ -326,7 +330,7 @@ class VlsiLayout:
|
|||
layoutToAddSref.coordinates = offsetInLayoutUnits
|
||||
|
||||
if mirror or rotate:
|
||||
|
||||
|
||||
layoutToAddSref.transFlags = [0,0,0]
|
||||
# transFlags = (mirror around x-axis, magnification, rotation)
|
||||
# If magnification or rotation is true, it is the flags are then
|
||||
|
|
@ -352,8 +356,8 @@ class VlsiLayout:
|
|||
|
||||
#add the sref to the root structure
|
||||
self.structures[self.rootStructureName].srefs.append(layoutToAddSref)
|
||||
|
||||
def addBox(self,layerNumber=0, purposeNumber=None, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
|
||||
|
||||
def addBox(self,layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), width=1.0, height=1.0,center=False):
|
||||
"""
|
||||
Method to add a box to a layout
|
||||
"""
|
||||
|
|
@ -369,7 +373,7 @@ class VlsiLayout:
|
|||
(offsetInLayoutUnits[0],offsetInLayoutUnits[1]+heightInLayoutUnits),
|
||||
offsetInLayoutUnits]
|
||||
else:
|
||||
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
|
||||
startPoint = (offsetInLayoutUnits[0]-widthInLayoutUnits/2.0, offsetInLayoutUnits[1]-heightInLayoutUnits/2.0)
|
||||
coordinates=[startPoint,
|
||||
(startPoint[0]+widthInLayoutUnits,startPoint[1]),
|
||||
(startPoint[0]+widthInLayoutUnits,startPoint[1]+heightInLayoutUnits),
|
||||
|
|
@ -378,13 +382,12 @@ class VlsiLayout:
|
|||
|
||||
boundaryToAdd = GdsBoundary()
|
||||
boundaryToAdd.drawingLayer = layerNumber
|
||||
boundaryToAdd.dataType = 0
|
||||
boundaryToAdd.coordinates = coordinates
|
||||
boundaryToAdd.purposeLayer = purposeNumber
|
||||
#add the sref to the root structure
|
||||
self.structures[self.rootStructureName].boundaries.append(boundaryToAdd)
|
||||
|
||||
def addPath(self, layerNumber=0, purposeNumber = None, coordinates=[(0,0)], width=1.0):
|
||||
|
||||
def addPath(self, layerNumber=0, purposeNumber=0, coordinates=[(0,0)], width=1.0):
|
||||
"""
|
||||
Method to add a path to a layout
|
||||
"""
|
||||
|
|
@ -396,24 +399,21 @@ class VlsiLayout:
|
|||
cY = self.userUnits(coordinate[1])
|
||||
layoutUnitCoordinates.append((cX,cY))
|
||||
pathToAdd = GdsPath()
|
||||
pathToAdd.drawingLayer=layerNumber
|
||||
pathToAdd.drawingLayer = layerNumber
|
||||
pathToAdd.purposeLayer = purposeNumber
|
||||
pathToAdd.pathWidth=widthInLayoutUnits
|
||||
pathToAdd.coordinates=layoutUnitCoordinates
|
||||
pathToAdd.pathWidth = widthInLayoutUnits
|
||||
pathToAdd.coordinates = layoutUnitCoordinates
|
||||
#add the sref to the root structure
|
||||
self.structures[self.rootStructureName].paths.append(pathToAdd)
|
||||
|
||||
def addText(self, text, layerNumber=0, purposeNumber = None, offsetInMicrons=(0,0), magnification=0.1, rotate = None):
|
||||
|
||||
def addText(self, text, layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), magnification=0.1, rotate = None):
|
||||
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
|
||||
textToAdd = GdsText()
|
||||
textToAdd.drawingLayer = layerNumber
|
||||
textToAdd.purposeLayer = purposeNumber
|
||||
textToAdd.dataType = 0
|
||||
textToAdd.coordinates = [offsetInLayoutUnits]
|
||||
textToAdd.transFlags = [0,0,0]
|
||||
if(len(text)%2 == 1):
|
||||
text = text + '\x00'
|
||||
textToAdd.textString = text
|
||||
textToAdd.transFlags = [0,0,0]
|
||||
textToAdd.textString = self.padText(text)
|
||||
#textToAdd.transFlags[1] = 1
|
||||
textToAdd.magFactor = magnification
|
||||
if rotate:
|
||||
|
|
@ -421,7 +421,13 @@ class VlsiLayout:
|
|||
textToAdd.rotateAngle = rotate
|
||||
#add the sref to the root structure
|
||||
self.structures[self.rootStructureName].texts.append(textToAdd)
|
||||
|
||||
|
||||
def padText(self, text):
|
||||
if(len(text)%2 == 1):
|
||||
return text + '\x00'
|
||||
else:
|
||||
return text
|
||||
|
||||
def isBounded(self,testPoint,startPoint,endPoint):
|
||||
#these arguments are touples of (x,y) coordinates
|
||||
if testPoint == None:
|
||||
|
|
@ -433,7 +439,7 @@ class VlsiLayout:
|
|||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def intersectionPoint(self,startPoint1,endPoint1,startPoint2,endPoint2):
|
||||
if((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])!=0):
|
||||
pSlope = (endPoint1[1]-startPoint1[1])/(endPoint1[0]-startPoint1[0])
|
||||
|
|
@ -453,7 +459,7 @@ class VlsiLayout:
|
|||
newY = None
|
||||
elif((endPoint1[0]-startPoint1[0])==0 and (endPoint2[0]-startPoint2[0])!=0):
|
||||
qSlope = (endPoint2[1]-startPoint2[1])/(endPoint2[0]-startPoint2[0])
|
||||
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
|
||||
qIntercept = startPoint2[1]-qSlope*startPoint2[0]
|
||||
newX=endPoint1[0]
|
||||
newY=qSlope*newX+qIntercept
|
||||
elif((endPoint1[0]-startPoint1[0])!=0 and (endPoint2[0]-startPoint2[0])==0):
|
||||
|
|
@ -462,14 +468,14 @@ class VlsiLayout:
|
|||
newX=endPoint2[0]
|
||||
newY=pSlope*newX+pIntercept
|
||||
return (newX,newY)
|
||||
|
||||
|
||||
def isCollinear(self,testPoint,point1,point2):
|
||||
slope1 = (testPoint[1]-point1[1])/(testPoint[0]-point1[0])
|
||||
slope2 = (point2[1]-point1[1])/(point2[0]-point1[0])
|
||||
if slope1 == slope2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def doShapesIntersect(self,shape1Coordinates, shape2Coordinates):
|
||||
"""
|
||||
Utility function to determine if 2 arbitrary shapes intersect.
|
||||
|
|
@ -486,7 +492,7 @@ class VlsiLayout:
|
|||
if(self.isBounded(intersect,startPoint1,endPoint1) and self.isBounded(intersect,startPoint2,endPoint2)):
|
||||
return True #these shapes overlap!
|
||||
return False #these shapes are ok
|
||||
|
||||
|
||||
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
|
||||
"""
|
||||
Check if a point is contained in the shape
|
||||
|
|
@ -511,7 +517,7 @@ class VlsiLayout:
|
|||
pointCoordinates[1]<bottomBound):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
|
||||
"""
|
||||
Go through every point in the shape to test if they are all inside the box.
|
||||
|
|
@ -520,8 +526,8 @@ class VlsiLayout:
|
|||
if not self.isPointInsideOfBox(point,boxCoordinates):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
def fillAreaDensity(self, layerToFill = 0, offsetInMicrons = (0,0), coverageWidth = 100.0, coverageHeight = 100.0, minSpacing = 0.22, blockSize = 1.0):
|
||||
effectiveBlock = blockSize+minSpacing
|
||||
widthInBlocks = int(coverageWidth/effectiveBlock)
|
||||
|
|
@ -540,7 +546,7 @@ class VlsiLayout:
|
|||
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
|
||||
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
||||
if joint:
|
||||
self.tempPassFail = False
|
||||
self.tempPassFail = False
|
||||
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
||||
if common:
|
||||
self.tempPassFail = False
|
||||
|
|
@ -553,11 +559,11 @@ class VlsiLayout:
|
|||
shiftedBoundaryCoordinates.append((shapeCoordinate[0]+coordinates[0],shapeCoordinate[1]+coordinates[1]))
|
||||
joint = self.doShapesIntersect(self.tempCoordinates, shiftedBoundaryCoordinates)
|
||||
if joint:
|
||||
self.tempPassFail = False
|
||||
self.tempPassFail = False
|
||||
common = self.isShapeInsideOfBox(shiftedBoundaryCoordinates,self.tempCoordinates)
|
||||
if common:
|
||||
self.tempPassFail = False
|
||||
|
||||
|
||||
for yIndex in range(0,heightInBlocks):
|
||||
for xIndex in range(0,widthInBlocks):
|
||||
percentDone = (float((yIndex*heightInBlocks)+xIndex) / (heightInBlocks*widthInBlocks))*100
|
||||
|
|
@ -576,7 +582,7 @@ class VlsiLayout:
|
|||
passFailRecord.append(self.tempPassFail)
|
||||
print("Percent Complete:"+str(percentDone))
|
||||
|
||||
|
||||
|
||||
passFailIndex=0
|
||||
for yIndex in range(0,heightInBlocks):
|
||||
for xIndex in range(0,widthInBlocks):
|
||||
|
|
@ -587,41 +593,50 @@ class VlsiLayout:
|
|||
passFailIndex += 1
|
||||
print("Done\n\n")
|
||||
|
||||
def getLayoutBorder(self,borderlayer):
|
||||
cellSizeMicron=None
|
||||
def getLayoutBorder(self, lpp):
|
||||
cellSizeMicron = None
|
||||
for boundary in self.structures[self.rootStructureName].boundaries:
|
||||
if boundary.drawingLayer==borderlayer:
|
||||
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
|
||||
lpp):
|
||||
if self.debug:
|
||||
debug.info(1,"Find border "+str(boundary.coordinates))
|
||||
left_bottom=boundary.coordinates[0]
|
||||
right_top=boundary.coordinates[2]
|
||||
cellSize=[right_top[0]-left_bottom[0],right_top[1]-left_bottom[1]]
|
||||
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
|
||||
if not(cellSizeMicron):
|
||||
print("Error: "+str(self.rootStructureName)+".cell_size information not found yet")
|
||||
debug.info(1, "Find border "+str(boundary.coordinates))
|
||||
left_bottom = boundary.coordinates[0]
|
||||
right_top = boundary.coordinates[2]
|
||||
cellSize = [right_top[0]-left_bottom[0],
|
||||
right_top[1]-left_bottom[1]]
|
||||
cellSizeMicron = [cellSize[0]*self.units[0],
|
||||
cellSize[1]*self.units[0]]
|
||||
debug.check(cellSizeMicron,
|
||||
"Error: "+str(self.rootStructureName)+".cell_size information not found yet")
|
||||
|
||||
return cellSizeMicron
|
||||
|
||||
def measureSize(self,startStructure):
|
||||
self.rootStructureName=startStructure
|
||||
def measureSize(self, startStructure):
|
||||
self.rootStructureName = self.padText(startStructure)
|
||||
self.populateCoordinateMap()
|
||||
cellBoundary = [None, None, None, None]
|
||||
for TreeUnit in self.xyTree:
|
||||
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
|
||||
cellSize=[cellBoundary[2]-cellBoundary[0],cellBoundary[3]-cellBoundary[1]]
|
||||
cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]]
|
||||
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
|
||||
cellSize = [cellBoundary[2]-cellBoundary[0],
|
||||
cellBoundary[3]-cellBoundary[1]]
|
||||
cellSizeMicron = [cellSize[0]*self.units[0],
|
||||
cellSize[1]*self.units[0]]
|
||||
return cellSizeMicron
|
||||
|
||||
def measureBoundary(self,startStructure):
|
||||
self.rootStructureName=startStructure
|
||||
def measureBoundary(self, startStructure):
|
||||
self.rootStructureName = self.padText(startStructure)
|
||||
self.populateCoordinateMap()
|
||||
cellBoundary = [None, None, None, None]
|
||||
for TreeUnit in self.xyTree:
|
||||
cellBoundary=self.measureSizeInStructure(TreeUnit,cellBoundary)
|
||||
return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]],
|
||||
[self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]]
|
||||
|
||||
def measureSizeInStructure(self,structure,cellBoundary):
|
||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||
cellBoundary = self.measureSizeInStructure(TreeUnit, cellBoundary)
|
||||
return [[self.units[0]*cellBoundary[0],
|
||||
self.units[0]*cellBoundary[1]],
|
||||
[self.units[0]*cellBoundary[2],
|
||||
self.units[0]*cellBoundary[3]]]
|
||||
|
||||
def measureSizeInStructure(self, structure, cellBoundary):
|
||||
(structureName, structureOrigin,
|
||||
structureuVector, structurevVector) = structure
|
||||
for boundary in self.structures[str(structureName)].boundaries:
|
||||
left_bottom=boundary.coordinates[0]
|
||||
right_top=boundary.coordinates[2]
|
||||
|
|
@ -631,7 +646,7 @@ class VlsiLayout:
|
|||
thisBoundary[2]+structureOrigin[0],thisBoundary[3]+structureOrigin[1]]
|
||||
cellBoundary=self.updateBoundary(thisBoundary,cellBoundary)
|
||||
return cellBoundary
|
||||
|
||||
|
||||
def updateBoundary(self,thisBoundary,cellBoundary):
|
||||
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
|
||||
# If any are None
|
||||
|
|
@ -648,17 +663,17 @@ class VlsiLayout:
|
|||
cellBoundary[3]=right_top_Y
|
||||
return cellBoundary
|
||||
|
||||
|
||||
def getTexts(self, layer):
|
||||
def getTexts(self, lpp):
|
||||
"""
|
||||
Get all of the labels on a given layer only at the root level.
|
||||
"""
|
||||
text_list = []
|
||||
for Text in self.structures[self.rootStructureName].texts:
|
||||
if Text.drawingLayer == layer:
|
||||
if sameLPP((Text.drawingLayer, Text.purposeLayer),
|
||||
lpp):
|
||||
text_list.append(Text)
|
||||
return text_list
|
||||
|
||||
|
||||
def getPinShape(self, pin_name):
|
||||
"""
|
||||
Search for a pin label and return the largest enclosing rectangle
|
||||
|
|
@ -671,15 +686,15 @@ class VlsiLayout:
|
|||
max_pin = None
|
||||
max_area = 0
|
||||
for pin in pin_list:
|
||||
(layer,boundary) = pin
|
||||
(layer, boundary) = pin
|
||||
new_area = boundaryArea(boundary)
|
||||
if max_pin == None or new_area>max_area:
|
||||
if not max_pin or new_area > max_area:
|
||||
max_pin = pin
|
||||
max_area = new_area
|
||||
max_pins.append(max_pin)
|
||||
|
||||
return max_pins
|
||||
|
||||
|
||||
|
||||
def getAllPinShapes(self, pin_name):
|
||||
"""
|
||||
|
|
@ -690,32 +705,33 @@ class VlsiLayout:
|
|||
pin_map = self.pins[pin_name]
|
||||
for pin_list in pin_map:
|
||||
for pin in pin_list:
|
||||
(pin_layer, boundary) = pin
|
||||
(pin_layer, boundary) = pin
|
||||
shape_list.append(pin)
|
||||
|
||||
return shape_list
|
||||
|
||||
|
||||
def processLabelPins(self, layer):
|
||||
def processLabelPins(self, lpp):
|
||||
"""
|
||||
Find all text labels and create a map to a list of shapes that
|
||||
they enclose on the given layer.
|
||||
"""
|
||||
# Get the labels on a layer in the root level
|
||||
labels = self.getTexts(layer)
|
||||
labels = self.getTexts(lpp)
|
||||
|
||||
# Get all of the shapes on the layer at all levels
|
||||
# and transform them to the current level
|
||||
shapes = self.getAllShapes(layer)
|
||||
shapes = self.getAllShapes(lpp)
|
||||
|
||||
for label in labels:
|
||||
label_coordinate = label.coordinates[0]
|
||||
user_coordinate = [x*self.units[0] for x in label_coordinate]
|
||||
pin_shapes = []
|
||||
for boundary in shapes:
|
||||
if self.labelInRectangle(user_coordinate,boundary):
|
||||
pin_shapes.append((layer, boundary))
|
||||
if self.labelInRectangle(user_coordinate, boundary):
|
||||
pin_shapes.append((lpp, boundary))
|
||||
|
||||
label_text = label.textString
|
||||
|
||||
# Remove the padding if it exists
|
||||
if label_text[-1] == "\x00":
|
||||
label_text = label_text[0:-1]
|
||||
|
|
@ -725,81 +741,97 @@ class VlsiLayout:
|
|||
except KeyError:
|
||||
self.pins[label_text] = []
|
||||
self.pins[label_text].append(pin_shapes)
|
||||
|
||||
|
||||
def getBlockages(self,layer):
|
||||
|
||||
def getBlockages(self, lpp):
|
||||
"""
|
||||
Return all blockages on a given layer in [coordinate 1, coordinate 2,...] format and
|
||||
Return all blockages on a given layer in
|
||||
[coordinate 1, coordinate 2,...] format and
|
||||
user units.
|
||||
"""
|
||||
blockages = []
|
||||
|
||||
shapes = self.getAllShapes(layer)
|
||||
shapes = self.getAllShapes(lpp)
|
||||
for boundary in shapes:
|
||||
vectors = []
|
||||
for i in range(0,len(boundary),2):
|
||||
vectors.append(vector(boundary[i],boundary[i+1]))
|
||||
for i in range(0, len(boundary), 2):
|
||||
vectors.append((boundary[i], boundary[i+1]))
|
||||
blockages.append(vectors)
|
||||
return blockages
|
||||
|
||||
def getAllShapes(self,layer):
|
||||
return blockages
|
||||
|
||||
def getAllShapes(self, lpp):
|
||||
"""
|
||||
Return all shapes on a given layer in [llx, lly, urx, ury] format and user units for rectangles
|
||||
and [coordinate 1, coordinate 2,...] format and user units for polygons.
|
||||
Return all shapes on a given layer in [llx, lly, urx, ury]
|
||||
format and user units for rectangles
|
||||
and [coordinate 1, coordinate 2,...] format and user
|
||||
units for polygons.
|
||||
"""
|
||||
boundaries = set()
|
||||
for TreeUnit in self.xyTree:
|
||||
#print(TreeUnit[0])
|
||||
boundaries.update(self.getShapesInStructure(layer,TreeUnit))
|
||||
# print(TreeUnit[0])
|
||||
boundaries.update(self.getShapesInStructure(lpp, TreeUnit))
|
||||
|
||||
# Convert to user units
|
||||
user_boundaries = []
|
||||
for boundary in boundaries:
|
||||
boundaries_list = []
|
||||
for i in range(0,len(boundary)):
|
||||
for i in range(0, len(boundary)):
|
||||
boundaries_list.append(boundary[i]*self.units[0])
|
||||
user_boundaries.append(boundaries_list)
|
||||
return user_boundaries
|
||||
|
||||
|
||||
def getShapesInStructure(self,layer,structure):
|
||||
"""
|
||||
Go through all the shapes in a structure and return the list of shapes in
|
||||
the form [llx, lly, urx, ury] for rectangles and [coordinate 1, coordinate 2,...] for polygons.
|
||||
def getShapesInStructure(self, lpp, structure):
|
||||
"""
|
||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||
#print(structureName,"u",structureuVector.transpose(),"v",structurevVector.transpose(),"o",structureOrigin.transpose())
|
||||
Go through all the shapes in a structure and
|
||||
return the list of shapes in
|
||||
the form [llx, lly, urx, ury] for rectangles
|
||||
and [coordinate 1, coordinate 2,...] for polygons.
|
||||
"""
|
||||
(structureName, structureOrigin,
|
||||
structureuVector, structurevVector) = structure
|
||||
# print(structureName,
|
||||
# "u", structureuVector.transpose(),
|
||||
# "v",structurevVector.transpose(),
|
||||
# "o",structureOrigin.transpose())
|
||||
boundaries = []
|
||||
for boundary in self.structures[str(structureName)].boundaries:
|
||||
if layer==boundary.drawingLayer:
|
||||
if len(boundary.coordinates)!=5:
|
||||
if sameLPP((boundary.drawingLayer, boundary.purposeLayer),
|
||||
lpp):
|
||||
if len(boundary.coordinates) != 5:
|
||||
# if shape is a polygon (used in DFF)
|
||||
boundaryPolygon = []
|
||||
# Polygon is a list of coordinates going ccw
|
||||
for coord in range(0,len(boundary.coordinates)):
|
||||
for coord in range(0, len(boundary.coordinates)):
|
||||
boundaryPolygon.append(boundary.coordinates[coord][0])
|
||||
boundaryPolygon.append(boundary.coordinates[coord][1])
|
||||
# perform the rotation
|
||||
boundaryPolygon=self.transformPolygon(boundaryPolygon,structureuVector,structurevVector)
|
||||
# add the offset
|
||||
boundaryPolygon = self.transformPolygon(boundaryPolygon,
|
||||
structureuVector,
|
||||
structurevVector)
|
||||
# add the offset
|
||||
polygon = []
|
||||
for i in range(0,len(boundaryPolygon),2):
|
||||
polygon.append(boundaryPolygon[i]+structureOrigin[0].item())
|
||||
polygon.append(boundaryPolygon[i+1]+structureOrigin[1].item())
|
||||
for i in range(0, len(boundaryPolygon), 2):
|
||||
polygon.append(boundaryPolygon[i] + structureOrigin[0].item())
|
||||
polygon.append(boundaryPolygon[i+1] + structureOrigin[1].item())
|
||||
# make it a tuple
|
||||
polygon = tuple(polygon)
|
||||
boundaries.append(polygon)
|
||||
else:
|
||||
# else shape is a rectangle
|
||||
left_bottom=boundary.coordinates[0]
|
||||
right_top=boundary.coordinates[2]
|
||||
left_bottom = boundary.coordinates[0]
|
||||
right_top = boundary.coordinates[2]
|
||||
# Rectangle is [leftx, bottomy, rightx, topy].
|
||||
boundaryRect=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]]
|
||||
boundaryRect = [left_bottom[0], left_bottom[1],
|
||||
right_top[0], right_top[1]]
|
||||
# perform the rotation
|
||||
boundaryRect=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
|
||||
boundaryRect = self.transformRectangle(boundaryRect,
|
||||
structureuVector,
|
||||
structurevVector)
|
||||
# add the offset and make it a tuple
|
||||
boundaryRect=(boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
|
||||
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item())
|
||||
boundaryRect = (boundaryRect[0]+structureOrigin[0].item(),
|
||||
boundaryRect[1]+structureOrigin[1].item(),
|
||||
boundaryRect[2]+structureOrigin[0].item(),
|
||||
boundaryRect[3]+structureOrigin[1].item())
|
||||
boundaries.append(boundaryRect)
|
||||
return boundaries
|
||||
|
||||
|
|
@ -839,7 +871,7 @@ class VlsiLayout:
|
|||
"""
|
||||
Rotate a coordinate in space.
|
||||
"""
|
||||
# MRG: 9/3/18 Incorrect matrix multiplication!
|
||||
# MRG: 9/3/18 Incorrect matrix multiplication!
|
||||
# This is fixed to be:
|
||||
# |u[0] v[0]| |x| |x'|
|
||||
# |u[1] v[1]|x|y|=|y'|
|
||||
|
|
@ -861,11 +893,21 @@ class VlsiLayout:
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def sameLPP(lpp1, lpp2):
|
||||
"""
|
||||
Check if the layers and purposes are the same.
|
||||
Ignore if purpose is a None.
|
||||
"""
|
||||
if lpp1[1] == None or lpp2[1] == None:
|
||||
return lpp1[0] == lpp2[0]
|
||||
|
||||
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|
||||
|
||||
|
||||
def boundaryArea(A):
|
||||
"""
|
||||
Returns boundary area for sorting.
|
||||
"""
|
||||
area_A=(A[2]-A[0])*(A[3]-A[1])
|
||||
return area_A
|
||||
|
||||
|
|
|
|||
|
|
@ -52,11 +52,12 @@ import sram
|
|||
class fake_sram(sram.sram):
|
||||
""" This is an SRAM that doesn't actually create itself, just computes
|
||||
the sizes. """
|
||||
def __init__(self, word_size, num_words, num_banks, name):
|
||||
def __init__(self, word_size, num_words, num_banks, name, num_spare_rows):
|
||||
self.name = name
|
||||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
self.num_banks = num_banks
|
||||
self.num_spare_rows = num_spare_rows
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
self.bitcell = self.mod_bitcell()
|
||||
|
|
@ -75,7 +76,10 @@ d.period = period
|
|||
# Set the load of outputs and slew of inputs
|
||||
d.set_load_slew(load,slew)
|
||||
# Set the probe address/bit
|
||||
probe_address = "1" * sram.addr_size
|
||||
if (self.num_spare_rows == 0):
|
||||
probe_address = "1" * sram.addr_size
|
||||
else:
|
||||
probe_address = "0" + ("1" * sram.addr_size - 1)
|
||||
probe_data = sram.word_size - 1
|
||||
d.set_probe(probe_address, probe_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
"""
|
||||
This is called globals.py, but it actually parses all the arguments and performs
|
||||
the global OpenRAM setup as well.
|
||||
This is called globals.py, but it actually parses all the arguments
|
||||
and performs the global OpenRAM setup as well.
|
||||
"""
|
||||
import os
|
||||
import debug
|
||||
|
|
@ -19,12 +19,13 @@ import re
|
|||
import copy
|
||||
import importlib
|
||||
|
||||
VERSION = "1.1.2"
|
||||
VERSION = "1.1.5"
|
||||
NAME = "OpenRAM v{}".format(VERSION)
|
||||
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
|
||||
|
||||
OPTS = options.options()
|
||||
CHECKPOINT_OPTS=None
|
||||
CHECKPOINT_OPTS = None
|
||||
|
||||
|
||||
def parse_args():
|
||||
""" Parse the optional arguments for OpenRAM """
|
||||
|
|
@ -32,27 +33,55 @@ def parse_args():
|
|||
global OPTS
|
||||
|
||||
option_list = {
|
||||
optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex",
|
||||
optparse.make_option("-b",
|
||||
"--backannotated",
|
||||
action="store_true",
|
||||
dest="use_pex",
|
||||
help="Back annotate simulation"),
|
||||
optparse.make_option("-o", "--output", dest="output_name",
|
||||
help="Base output file name(s) prefix", metavar="FILE"),
|
||||
optparse.make_option("-p", "--outpath", dest="output_path",
|
||||
optparse.make_option("-o",
|
||||
"--output",
|
||||
dest="output_name",
|
||||
help="Base output file name(s) prefix",
|
||||
metavar="FILE"),
|
||||
optparse.make_option("-p", "--outpath",
|
||||
dest="output_path",
|
||||
help="Output file(s) location"),
|
||||
optparse.make_option("-i", "--inlinecheck", action="store_true",
|
||||
help="Enable inline LVS/DRC checks", dest="inline_lvsdrc"),
|
||||
optparse.make_option("-n", "--nocheck", action="store_false",
|
||||
help="Disable all LVS/DRC checks", dest="check_lvsdrc"),
|
||||
optparse.make_option("-v", "--verbose", action="count", dest="debug_level",
|
||||
optparse.make_option("-i",
|
||||
"--inlinecheck",
|
||||
action="store_true",
|
||||
help="Enable inline LVS/DRC checks",
|
||||
dest="inline_lvsdrc"),
|
||||
optparse.make_option("-n", "--nocheck",
|
||||
action="store_false",
|
||||
help="Disable all LVS/DRC checks",
|
||||
dest="check_lvsdrc"),
|
||||
optparse.make_option("-v",
|
||||
"--verbose",
|
||||
action="count",
|
||||
dest="debug_level",
|
||||
help="Increase the verbosity level"),
|
||||
optparse.make_option("-t", "--tech", dest="tech_name",
|
||||
optparse.make_option("-t",
|
||||
"--tech",
|
||||
dest="tech_name",
|
||||
help="Technology name"),
|
||||
optparse.make_option("-s", "--spice", dest="spice_name",
|
||||
optparse.make_option("-s",
|
||||
"--spice",
|
||||
dest="spice_name",
|
||||
help="Spice simulator executable name"),
|
||||
optparse.make_option("-r", "--remove_netlist_trimming", action="store_false", dest="trim_netlist",
|
||||
optparse.make_option("-r",
|
||||
"--remove_netlist_trimming",
|
||||
action="store_false",
|
||||
dest="trim_netlist",
|
||||
help="Disable removal of noncritical memory cells during characterization"),
|
||||
optparse.make_option("-c", "--characterize", action="store_false", dest="analytical_delay",
|
||||
optparse.make_option("-c",
|
||||
"--characterize",
|
||||
action="store_false",
|
||||
dest="analytical_delay",
|
||||
help="Perform characterization to calculate delays (default is analytical models)"),
|
||||
optparse.make_option("-d", "--dontpurge", action="store_false", dest="purge_temp",
|
||||
optparse.make_option("-d",
|
||||
"--dontpurge",
|
||||
action="store_false",
|
||||
dest="purge_temp",
|
||||
help="Don't purge the contents of the temp directory after a successful run")
|
||||
# -h --help is implicit.
|
||||
}
|
||||
|
|
@ -70,9 +99,13 @@ def parse_args():
|
|||
# Alias SCMOS to 180nm
|
||||
if OPTS.tech_name == "scmos":
|
||||
OPTS.tech_name = "scn4m_subm"
|
||||
# Alias s8 to sky130
|
||||
if OPTS.tech_name == "s8":
|
||||
OPTS.tech_name = "sky130"
|
||||
|
||||
return (options, args)
|
||||
|
||||
|
||||
def print_banner():
|
||||
""" Conditionally print the banner to stdout """
|
||||
global OPTS
|
||||
|
|
@ -117,13 +150,13 @@ def check_versions():
|
|||
except:
|
||||
OPTS.coverage = 0
|
||||
|
||||
|
||||
def init_openram(config_file, is_unit_test=True):
|
||||
"""Initialize the technology, paths, simulators, etc."""
|
||||
""" Initialize the technology, paths, simulators, etc. """
|
||||
|
||||
|
||||
check_versions()
|
||||
|
||||
debug.info(1,"Initializing OpenRAM...")
|
||||
debug.info(1, "Initializing OpenRAM...")
|
||||
|
||||
setup_paths()
|
||||
|
||||
|
|
@ -138,18 +171,17 @@ def init_openram(config_file, is_unit_test=True):
|
|||
from sram_factory import factory
|
||||
factory.reset()
|
||||
|
||||
setup_bitcell()
|
||||
|
||||
global OPTS
|
||||
global CHECKPOINT_OPTS
|
||||
|
||||
# This is a hack. If we are running a unit test and have checkpointed
|
||||
# the options, load them rather than reading the config file.
|
||||
# This way, the configuration is reloaded at the start of every unit test.
|
||||
# If a unit test fails, we don't have to worry about restoring the old config values
|
||||
# If a unit test fails,
|
||||
# we don't have to worry about restoring the old config values
|
||||
# that may have been tested.
|
||||
if is_unit_test and CHECKPOINT_OPTS:
|
||||
OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy()
|
||||
OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy()
|
||||
return
|
||||
|
||||
# Import these to find the executables for checkpointing
|
||||
|
|
@ -159,7 +191,8 @@ def init_openram(config_file, is_unit_test=True):
|
|||
# after each unit test
|
||||
if not CHECKPOINT_OPTS:
|
||||
CHECKPOINT_OPTS = copy.copy(OPTS)
|
||||
|
||||
|
||||
|
||||
def setup_bitcell():
|
||||
"""
|
||||
Determine the correct custom or parameterized bitcell for the design.
|
||||
|
|
@ -169,44 +202,36 @@ def setup_bitcell():
|
|||
# If we have non-1rw ports,
|
||||
# and the user didn't over-ride the bitcell manually,
|
||||
# figure out the right bitcell to use
|
||||
if (OPTS.bitcell=="bitcell"):
|
||||
if (OPTS.bitcell == "bitcell"):
|
||||
|
||||
if (OPTS.num_rw_ports==1 and OPTS.num_w_ports==0 and OPTS.num_r_ports==0):
|
||||
if (OPTS.num_rw_ports == 1 and OPTS.num_w_ports == 0 and OPTS.num_r_ports == 0):
|
||||
OPTS.bitcell = "bitcell"
|
||||
OPTS.replica_bitcell = "replica_bitcell"
|
||||
OPTS.dummy_bitcell = "dummy_bitcell"
|
||||
else:
|
||||
ports = ""
|
||||
if OPTS.num_rw_ports>0:
|
||||
if OPTS.num_rw_ports > 0:
|
||||
ports += "{}rw_".format(OPTS.num_rw_ports)
|
||||
if OPTS.num_w_ports>0:
|
||||
if OPTS.num_w_ports > 0:
|
||||
ports += "{}w_".format(OPTS.num_w_ports)
|
||||
if OPTS.num_r_ports>0:
|
||||
if OPTS.num_r_ports > 0:
|
||||
ports += "{}r".format(OPTS.num_r_ports)
|
||||
|
||||
OPTS.bitcell = "bitcell_"+ports
|
||||
OPTS.replica_bitcell = "replica_bitcell_"+ports
|
||||
OPTS.dummy_bitcell = "dummy_bitcell_"+ports
|
||||
else:
|
||||
OPTS.replica_bitcell = "replica_" + OPTS.bitcell
|
||||
OPTS.replica_bitcell = "dummy_" + OPTS.bitcell
|
||||
if ports != "":
|
||||
OPTS.bitcell_suffix = "_" + ports
|
||||
OPTS.bitcell = "bitcell" + OPTS.bitcell_suffix
|
||||
|
||||
# See if bitcell exists
|
||||
from importlib import find_loader
|
||||
try:
|
||||
__import__(OPTS.bitcell)
|
||||
__import__(OPTS.replica_bitcell)
|
||||
__import__(OPTS.dummy_bitcell)
|
||||
except ImportError:
|
||||
# Use the pbitcell if we couldn't find a custom bitcell
|
||||
# or its custom replica bitcell
|
||||
# Use the pbitcell (and give a warning if not in unit test mode)
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell = "replica_pbitcell"
|
||||
OPTS.replica_bitcell = "dummy_pbitcell"
|
||||
if not OPTS.is_unit_test:
|
||||
debug.warning("Using the parameterized bitcell which may have suboptimal density.")
|
||||
debug.info(1,"Using bitcell: {}".format(OPTS.bitcell))
|
||||
debug.info(1, "Using bitcell: {}".format(OPTS.bitcell))
|
||||
|
||||
|
||||
def get_tool(tool_type, preferences, default_name=None):
|
||||
|
|
@ -215,63 +240,75 @@ def get_tool(tool_type, preferences, default_name=None):
|
|||
one selected and its full path. If default is specified,
|
||||
find that one only and error otherwise.
|
||||
"""
|
||||
debug.info(2,"Finding {} tool...".format(tool_type))
|
||||
debug.info(2, "Finding {} tool...".format(tool_type))
|
||||
|
||||
if default_name:
|
||||
exe_name=find_exe(default_name)
|
||||
exe_name = find_exe(default_name)
|
||||
if exe_name == None:
|
||||
debug.error("{0} not found. Cannot find {1} tool.".format(default_name,tool_type),2)
|
||||
debug.error("{0} not found. Cannot find {1} tool.".format(default_name,
|
||||
tool_type),
|
||||
2)
|
||||
else:
|
||||
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
||||
return(default_name,exe_name)
|
||||
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
|
||||
return(default_name, exe_name)
|
||||
else:
|
||||
for name in preferences:
|
||||
exe_name = find_exe(name)
|
||||
if exe_name != None:
|
||||
debug.info(1, "Using {0}: {1}".format(tool_type,exe_name))
|
||||
return(name,exe_name)
|
||||
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
|
||||
return(name, exe_name)
|
||||
else:
|
||||
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name,tool_type))
|
||||
debug.info(1,
|
||||
"Could not find {0}, trying next {1} tool.".format(name,
|
||||
tool_type))
|
||||
else:
|
||||
return(None,"")
|
||||
return(None, "")
|
||||
|
||||
|
||||
def read_config(config_file, is_unit_test=True):
|
||||
"""
|
||||
"""
|
||||
Read the configuration file that defines a few parameters. The
|
||||
config file is just a Python file that defines some config
|
||||
options. This will only actually get read the first time. Subsequent
|
||||
reads will just restore the previous copy (ask mrg)
|
||||
"""
|
||||
global OPTS
|
||||
|
||||
# Create a full path relative to current dir unless it is already an abs path
|
||||
|
||||
# it is already not an abs path, make it one
|
||||
if not os.path.isabs(config_file):
|
||||
config_file = os.getcwd() + "/" + config_file
|
||||
|
||||
# Make it a python file if the base name was only given
|
||||
config_file = re.sub(r'\.py$', "", config_file)
|
||||
|
||||
|
||||
# Expand the user if it is used
|
||||
config_file = os.path.expanduser(config_file)
|
||||
OPTS.config_file = config_file
|
||||
# Add the path to the system path so we can import things in the other directory
|
||||
|
||||
OPTS.config_file = config_file + ".py"
|
||||
# Add the path to the system path
|
||||
# so we can import things in the other directory
|
||||
dir_name = os.path.dirname(config_file)
|
||||
file_name = os.path.basename(config_file)
|
||||
module_name = os.path.basename(config_file)
|
||||
|
||||
# Prepend the path to avoid if we are using the example config
|
||||
sys.path.insert(0,dir_name)
|
||||
sys.path.insert(0, dir_name)
|
||||
# Import the configuration file of which modules to use
|
||||
debug.info(1, "Configuration file is " + config_file + ".py")
|
||||
try:
|
||||
config = importlib.import_module(file_name)
|
||||
config = importlib.import_module(module_name)
|
||||
except:
|
||||
debug.error("Unable to read configuration file: {0}".format(config_file),2)
|
||||
|
||||
for k,v in config.__dict__.items():
|
||||
OPTS.overridden = {}
|
||||
for k, v in config.__dict__.items():
|
||||
# The command line will over-ride the config file
|
||||
# except in the case of the tech name! This is because the tech name
|
||||
# is sometimes used to specify the config file itself (e.g. unit tests)
|
||||
# Note that if we re-read a config file, nothing will get read again!
|
||||
if not k in OPTS.__dict__ or k=="tech_name":
|
||||
OPTS.__dict__[k]=v
|
||||
if k not in OPTS.__dict__ or k == "tech_name":
|
||||
OPTS.__dict__[k] = v
|
||||
OPTS.overridden[k] = True
|
||||
|
||||
# Massage the output path to be an absolute one
|
||||
if not OPTS.output_path.endswith('/'):
|
||||
|
|
@ -281,20 +318,20 @@ def read_config(config_file, is_unit_test=True):
|
|||
debug.info(1, "Output saved in " + OPTS.output_path)
|
||||
|
||||
# Remember if we are running unit tests to reduce output
|
||||
OPTS.is_unit_test=is_unit_test
|
||||
OPTS.is_unit_test = is_unit_test
|
||||
|
||||
# If we are only generating a netlist, we can't do DRC/LVS
|
||||
if OPTS.netlist_only:
|
||||
OPTS.check_lvsdrc=False
|
||||
OPTS.check_lvsdrc = False
|
||||
|
||||
# If config didn't set output name, make a reasonable default.
|
||||
if (OPTS.output_name == ""):
|
||||
ports = ""
|
||||
if OPTS.num_rw_ports>0:
|
||||
if OPTS.num_rw_ports > 0:
|
||||
ports += "{}rw_".format(OPTS.num_rw_ports)
|
||||
if OPTS.num_w_ports>0:
|
||||
if OPTS.num_w_ports > 0:
|
||||
ports += "{}w_".format(OPTS.num_w_ports)
|
||||
if OPTS.num_r_ports>0:
|
||||
if OPTS.num_r_ports > 0:
|
||||
ports += "{}r_".format(OPTS.num_r_ports)
|
||||
OPTS.output_name = "sram_{0}b_{1}_{2}{3}".format(OPTS.word_size,
|
||||
OPTS.num_words,
|
||||
|
|
@ -302,8 +339,6 @@ def read_config(config_file, is_unit_test=True):
|
|||
OPTS.tech_name)
|
||||
|
||||
|
||||
|
||||
|
||||
def end_openram():
|
||||
""" Clean up openram for a proper exit """
|
||||
cleanup_paths()
|
||||
|
|
@ -312,9 +347,7 @@ def end_openram():
|
|||
import verify
|
||||
verify.print_drc_stats()
|
||||
verify.print_lvs_stats()
|
||||
verify.print_pex_stats()
|
||||
|
||||
|
||||
verify.print_pex_stats()
|
||||
|
||||
|
||||
def cleanup_paths():
|
||||
|
|
@ -323,54 +356,62 @@ def cleanup_paths():
|
|||
"""
|
||||
global OPTS
|
||||
if not OPTS.purge_temp:
|
||||
debug.info(0,"Preserving temp directory: {}".format(OPTS.openram_temp))
|
||||
debug.info(0,
|
||||
"Preserving temp directory: {}".format(OPTS.openram_temp))
|
||||
return
|
||||
elif os.path.exists(OPTS.openram_temp):
|
||||
debug.info(1,"Purging temp directory: {}".format(OPTS.openram_temp))
|
||||
# This annoyingly means you have to re-cd into the directory each debug iteration
|
||||
#shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||
debug.info(1,
|
||||
"Purging temp directory: {}".format(OPTS.openram_temp))
|
||||
# This annoyingly means you have to re-cd into
|
||||
# the directory each debug iteration
|
||||
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
|
||||
contents = [os.path.join(OPTS.openram_temp, i) for i in os.listdir(OPTS.openram_temp)]
|
||||
for i in contents:
|
||||
if os.path.isfile(i) or os.path.islink(i):
|
||||
os.remove(i)
|
||||
else:
|
||||
shutil.rmtree(i)
|
||||
|
||||
|
||||
|
||||
def setup_paths():
|
||||
""" Set up the non-tech related paths. """
|
||||
debug.info(2,"Setting up paths...")
|
||||
debug.info(2, "Setting up paths...")
|
||||
|
||||
global OPTS
|
||||
|
||||
try:
|
||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||
except:
|
||||
debug.error("$OPENRAM_HOME is not properly defined.",1)
|
||||
debug.check(os.path.isdir(OPENRAM_HOME),"$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME))
|
||||
debug.error("$OPENRAM_HOME is not properly defined.", 1)
|
||||
debug.check(os.path.isdir(OPENRAM_HOME),
|
||||
"$OPENRAM_HOME does not exist: {0}".format(OPENRAM_HOME))
|
||||
|
||||
# Add all of the subdirs to the python path
|
||||
# These subdirs are modules and don't need to be added: characterizer, verify
|
||||
# These subdirs are modules and don't need
|
||||
# to be added: characterizer, verify
|
||||
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
|
||||
for subdir in subdirlist:
|
||||
full_path = "{0}/{1}".format(OPENRAM_HOME,subdir)
|
||||
full_path = "{0}/{1}".format(OPENRAM_HOME, subdir)
|
||||
debug.check(os.path.isdir(full_path),
|
||||
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))
|
||||
sys.path.append("{0}".format(full_path))
|
||||
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir, full_path))
|
||||
if "__pycache__" not in full_path:
|
||||
sys.path.append("{0}".format(full_path))
|
||||
|
||||
if not OPTS.openram_temp.endswith('/'):
|
||||
OPTS.openram_temp += "/"
|
||||
debug.info(1, "Temporary files saved in " + OPTS.openram_temp)
|
||||
|
||||
|
||||
|
||||
def is_exe(fpath):
|
||||
""" Return true if the given is an executable file that exists. """
|
||||
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def find_exe(check_exe):
|
||||
""" Check if the binary exists in any path dir and return the full path. """
|
||||
"""
|
||||
Check if the binary exists in any path dir
|
||||
and return the full path.
|
||||
"""
|
||||
# Check if the preferred spice option exists in the path
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
exe = os.path.join(path, check_exe)
|
||||
|
|
@ -379,12 +420,14 @@ def find_exe(check_exe):
|
|||
return exe
|
||||
return None
|
||||
|
||||
|
||||
def init_paths():
|
||||
""" Create the temp and output directory if it doesn't exist """
|
||||
|
||||
# make the directory if it doesn't exist
|
||||
try:
|
||||
debug.info(1,"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||
debug.info(1,
|
||||
"Creating temp directory: {}".format(OPTS.openram_temp))
|
||||
os.makedirs(OPTS.openram_temp, 0o750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
|
|
@ -398,35 +441,48 @@ def init_paths():
|
|||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(OPTS.output_path, 0o750)
|
||||
except:
|
||||
debug.error("Unable to make output directory.",-1)
|
||||
debug.error("Unable to make output directory.", -1)
|
||||
|
||||
|
||||
def set_default_corner():
|
||||
""" Set the default corner. """
|
||||
|
||||
|
||||
import tech
|
||||
# Set some default options now based on the technology...
|
||||
if (OPTS.process_corners == ""):
|
||||
OPTS.process_corners = tech.spice["fet_models"].keys()
|
||||
if OPTS.nominal_corner_only:
|
||||
OPTS.process_corners = ["TT"]
|
||||
else:
|
||||
OPTS.process_corners = tech.spice["fet_models"].keys()
|
||||
if (OPTS.supply_voltages == ""):
|
||||
OPTS.supply_voltages = tech.spice["supply_voltages"]
|
||||
if OPTS.nominal_corner_only:
|
||||
OPTS.supply_voltages = [tech.spice["supply_voltages"][1]]
|
||||
else:
|
||||
OPTS.supply_voltages = tech.spice["supply_voltages"]
|
||||
if (OPTS.temperatures == ""):
|
||||
OPTS.temperatures = tech.spice["temperatures"]
|
||||
if OPTS.nominal_corner_only:
|
||||
OPTS.temperatures = [tech.spice["temperatures"][1]]
|
||||
else:
|
||||
OPTS.temperatures = tech.spice["temperatures"]
|
||||
|
||||
|
||||
def import_tech():
|
||||
""" Dynamically adds the tech directory to the path and imports it. """
|
||||
global OPTS
|
||||
|
||||
debug.info(2,"Importing technology: " + OPTS.tech_name)
|
||||
debug.info(2,
|
||||
"Importing technology: " + OPTS.tech_name)
|
||||
|
||||
# environment variable should point to the technology dir
|
||||
try:
|
||||
OPENRAM_TECH = os.path.abspath(os.environ.get("OPENRAM_TECH"))
|
||||
except:
|
||||
debug.error("$OPENRAM_TECH environment variable is not defined.",1)
|
||||
debug.error("$OPENRAM_TECH environment variable is not defined.", 1)
|
||||
|
||||
# Add all of the paths
|
||||
for tech_path in OPENRAM_TECH.split(":"):
|
||||
debug.check(os.path.isdir(tech_path),"$OPENRAM_TECH does not exist: {0}".format(tech_path))
|
||||
debug.check(os.path.isdir(tech_path),
|
||||
"$OPENRAM_TECH does not exist: {0}".format(tech_path))
|
||||
sys.path.append(tech_path)
|
||||
debug.info(1, "Adding technology path: {}".format(tech_path))
|
||||
|
||||
|
|
@ -434,11 +490,10 @@ def import_tech():
|
|||
try:
|
||||
tech_mod = __import__(OPTS.tech_name)
|
||||
except ImportError:
|
||||
debug.error("Nonexistent technology_setup_file: {0}.py".format(filename), -1)
|
||||
debug.error("Nonexistent technology module: {0}".format(OPTS.tech_name), -1)
|
||||
|
||||
OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/"
|
||||
|
||||
|
||||
# Add the tech directory
|
||||
tech_path = OPTS.openram_tech
|
||||
sys.path.append(tech_path)
|
||||
|
|
@ -447,61 +502,68 @@ def import_tech():
|
|||
except ImportError:
|
||||
debug.error("Could not load tech module.", -1)
|
||||
|
||||
# Add custom modules of the technology to the path, if they exist
|
||||
custom_mod_path = os.path.join(tech_path, "modules/")
|
||||
if os.path.exists(custom_mod_path):
|
||||
sys.path.append(custom_mod_path)
|
||||
|
||||
|
||||
def print_time(name, now_time, last_time=None, indentation=2):
|
||||
""" Print a statement about the time delta. """
|
||||
global OPTS
|
||||
|
||||
# Don't print during testing
|
||||
if not OPTS.is_unit_test or OPTS.debug_level>0:
|
||||
if not OPTS.is_unit_test or OPTS.debug_level > 0:
|
||||
if last_time:
|
||||
time = str(round((now_time-last_time).total_seconds(),1)) + " seconds"
|
||||
else:
|
||||
time = now_time.strftime('%m/%d/%Y %H:%M:%S')
|
||||
debug.print_raw("{0} {1}: {2}".format("*"*indentation,name,time))
|
||||
debug.print_raw("{0} {1}: {2}".format("*"*indentation, name, time))
|
||||
|
||||
|
||||
def report_status():
|
||||
""" Check for valid arguments and report the info about the SRAM being generated """
|
||||
"""
|
||||
Check for valid arguments and report the
|
||||
info about the SRAM being generated
|
||||
"""
|
||||
global OPTS
|
||||
|
||||
# Check if all arguments are integers for bits, size, banks
|
||||
if type(OPTS.word_size)!=int:
|
||||
if type(OPTS.word_size) != int:
|
||||
debug.error("{0} is not an integer in config file.".format(OPTS.word_size))
|
||||
if type(OPTS.num_words)!=int:
|
||||
if type(OPTS.num_words) != int:
|
||||
debug.error("{0} is not an integer in config file.".format(OPTS.sram_size))
|
||||
if type(OPTS.write_size) is not int and OPTS.write_size is not None:
|
||||
debug.error("{0} is not an integer in config file.".format(OPTS.write_size))
|
||||
|
||||
|
||||
# If a write mask is specified by the user, the mask write size should be the same as
|
||||
# the word size so that an entire word is written at once.
|
||||
if OPTS.write_size is not None:
|
||||
if (OPTS.word_size % OPTS.write_size != 0):
|
||||
debug.error("Write size needs to be an integer multiple of word size.")
|
||||
# If write size is more than half of the word size, then it doesn't need a write mask. It would be writing
|
||||
# If write size is more than half of the word size,
|
||||
# then it doesn't need a write mask. It would be writing
|
||||
# the whole word.
|
||||
if (OPTS.write_size < 1 or OPTS.write_size > OPTS.word_size/2):
|
||||
debug.error("Write size needs to be between 1 bit and {0} bits/2.".format(OPTS.word_size))
|
||||
|
||||
|
||||
|
||||
if not OPTS.tech_name:
|
||||
debug.error("Tech name must be specified in config file.")
|
||||
|
||||
debug.print_raw("Technology: {0}".format(OPTS.tech_name))
|
||||
total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks
|
||||
debug.print_raw("Total size: {} bits".format(total_size))
|
||||
if total_size>=2**14:
|
||||
if total_size >= 2**14:
|
||||
debug.warning("Requesting such a large memory size ({0}) will have a large run-time. ".format(total_size) +
|
||||
"Consider using multiple smaller banks.")
|
||||
debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
|
||||
OPTS.num_words,
|
||||
OPTS.num_banks))
|
||||
OPTS.num_words,
|
||||
OPTS.num_banks))
|
||||
if (OPTS.write_size != OPTS.word_size):
|
||||
debug.print_raw("Write size: {}".format(OPTS.write_size))
|
||||
debug.print_raw("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports,
|
||||
OPTS.num_r_ports,
|
||||
OPTS.num_w_ports))
|
||||
OPTS.num_r_ports,
|
||||
OPTS.num_w_ports))
|
||||
|
||||
if OPTS.netlist_only:
|
||||
debug.print_raw("Netlist only mode (no physical design is being done, netlist_only=False to disable).")
|
||||
|
|
@ -518,9 +580,9 @@ def report_status():
|
|||
if OPTS.analytical_delay:
|
||||
debug.print_raw("Characterization is disabled (using analytical delay models) (analytical_delay=False to simulate).")
|
||||
else:
|
||||
if OPTS.spice_name!="":
|
||||
if OPTS.spice_name != "":
|
||||
debug.print_raw("Performing simulation-based characterization with {}".format(OPTS.spice_name))
|
||||
if OPTS.trim_netlist:
|
||||
debug.print_raw("Trimming netlist to speed up characterization (trim_netlist=False to disable).")
|
||||
|
||||
|
||||
if OPTS.nominal_corner_only:
|
||||
debug.print_raw("Only characterizing nominal corner.")
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -42,10 +42,12 @@ class bank_select(design.design):
|
|||
self.place_instances()
|
||||
self.route_instances()
|
||||
|
||||
self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width
|
||||
self.width = max([x.rx() for x in self.inv_inst])
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
|
||||
# Number of control lines in the bus
|
||||
|
|
@ -61,19 +63,18 @@ class bank_select(design.design):
|
|||
if (self.port == "rw") or (self.port == "r"):
|
||||
self.input_control_signals.append("s_en")
|
||||
# These will be outputs of the gaters if this is multibank
|
||||
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||
self.control_signals = ["gated_" + str for str in self.input_control_signals]
|
||||
|
||||
self.add_pin_list(self.input_control_signals, "INPUT")
|
||||
self.add_pin("bank_sel")
|
||||
self.add_pin_list(self.control_signals, "OUTPUT")
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Create modules for later instantiation """
|
||||
self.bitcell = factory.create(module_type="bitcell")
|
||||
|
||||
height = self.bitcell.height + drc("poly_to_active")
|
||||
self.dff = factory.create(module_type="dff")
|
||||
height = self.dff.height + drc("poly_to_active")
|
||||
|
||||
# 1x Inverter
|
||||
self.inv_sel = factory.create(module_type="pinv", height=height)
|
||||
|
|
@ -94,20 +95,15 @@ class bank_select(design.design):
|
|||
|
||||
def calculate_module_offsets(self):
|
||||
|
||||
self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_nand = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_inputs = 0
|
||||
|
||||
self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height
|
||||
# Include the M1 pitches for the supply rails and spacing
|
||||
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
|
||||
self.width = self.xoffset_inv + self.inv4x.width
|
||||
|
||||
def create_instances(self):
|
||||
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
mod=self.inv_sel)
|
||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||
|
||||
|
|
@ -124,36 +120,36 @@ class bank_select(design.design):
|
|||
# (writes occur on clk low)
|
||||
if input_name in ("clk_buf"):
|
||||
|
||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||
mod=self.nor2))
|
||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||
mod=self.nor2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel_bar",
|
||||
gated_name+"_temp_bar",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x_nor))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# the rest are AND (nand2+inv) gates
|
||||
else:
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
mod=self.nand2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel",
|
||||
gated_name+"_temp_bar",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
|
@ -176,9 +172,9 @@ class bank_select(design.design):
|
|||
if i == 0:
|
||||
y_offset = 0
|
||||
else:
|
||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i-1)
|
||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1)
|
||||
|
||||
if i%2:
|
||||
if i % 2:
|
||||
y_offset += self.inv4x.height
|
||||
mirror = "MX"
|
||||
else:
|
||||
|
|
@ -197,9 +193,8 @@ class bank_select(design.design):
|
|||
mirror=mirror)
|
||||
|
||||
# They all get inverters on the output
|
||||
inv_inst.place(offset=[self.xoffset_inv, y_offset],
|
||||
inv_inst.place(offset=[logic_inst.rx(), y_offset],
|
||||
mirror=mirror)
|
||||
|
||||
|
||||
def route_instances(self):
|
||||
|
||||
|
|
@ -208,72 +203,72 @@ class bank_select(design.design):
|
|||
xoffset_bank_sel = bank_sel_inv_pin.lx()
|
||||
bank_sel_line_pos = vector(xoffset_bank_sel, 0)
|
||||
bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint)
|
||||
self.add_path("metal2", [bank_sel_line_pos, bank_sel_line_end])
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=bank_sel_inv_pin.lc())
|
||||
self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=bank_sel_inv_pin.center())
|
||||
|
||||
# Route the pin to the left edge as well
|
||||
bank_sel_pin_pos=vector(0, 0)
|
||||
bank_sel_pin_end=vector(bank_sel_line_pos.x, bank_sel_pin_pos.y)
|
||||
self.add_layout_pin_segment_center(text="bank_sel",
|
||||
layer="metal3",
|
||||
layer="m3",
|
||||
start=bank_sel_pin_pos,
|
||||
end=bank_sel_pin_end)
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=bank_sel_pin_end,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
|
||||
# bank_sel_bar is vertical wire
|
||||
bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z")
|
||||
xoffset_bank_sel_bar = bank_sel_bar_pin.rx()
|
||||
self.add_label_pin(text="bank_sel_bar",
|
||||
layer="metal2",
|
||||
offset=vector(xoffset_bank_sel_bar, 0),
|
||||
layer="m2",
|
||||
offset=vector(xoffset_bank_sel_bar, 0),
|
||||
height=self.inv4x.height)
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=bank_sel_bar_pin.rc())
|
||||
|
||||
|
||||
for i in range(self.num_control_lines):
|
||||
|
||||
logic_inst = self.logic_inst[i]
|
||||
inv_inst = self.inv_inst[i]
|
||||
|
||||
input_name = self.input_control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
if input_name in ("clk_buf"):
|
||||
xoffset_bank_signal = xoffset_bank_sel_bar
|
||||
else:
|
||||
xoffset_bank_signal = xoffset_bank_sel
|
||||
|
||||
# Connect the logic output to inverter input
|
||||
pre = logic_inst.get_pin("Z").lc()
|
||||
out_position = logic_inst.get_pin("Z").rc() + vector(0.5*self.m1_width,0)
|
||||
in_position = inv_inst.get_pin("A").lc() + vector(0.5*self.m1_width,0)
|
||||
post = inv_inst.get_pin("A").rc()
|
||||
self.add_path("metal1", [pre, out_position, in_position, post])
|
||||
out_pin = logic_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = inv_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y)
|
||||
mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y)
|
||||
self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos])
|
||||
|
||||
|
||||
# Connect the logic B input to bank_sel/bank_sel_bar
|
||||
logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1m2.height,0)
|
||||
# Connect the logic B input to bank_sel / bank_sel_bar
|
||||
logic_pin = logic_inst.get_pin("B")
|
||||
logic_pos = logic_pin.center()
|
||||
input_pos = vector(xoffset_bank_signal, logic_pos.y)
|
||||
self.add_path("metal2",[logic_pos, input_pos])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
self.add_path("m3", [logic_pos, input_pos])
|
||||
self.add_via_center(self.m2_stack,
|
||||
input_pos)
|
||||
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=logic_pos)
|
||||
|
||||
|
||||
# Connect the logic A input to the input pin
|
||||
logic_pos = logic_inst.get_pin("A").lc()
|
||||
input_pos = vector(0,logic_pos.y)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
logic_pin = logic_inst.get_pin("A")
|
||||
logic_pos = logic_pin.center()
|
||||
input_pos = vector(0, logic_pos.y)
|
||||
self.add_via_stack_center(from_layer=logic_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=logic_pos)
|
||||
self.add_layout_pin_segment_center(text=input_name,
|
||||
layer="metal3",
|
||||
layer="m3",
|
||||
start=input_pos,
|
||||
end=logic_pos)
|
||||
|
||||
|
|
@ -285,7 +280,6 @@ class bank_select(design.design):
|
|||
width=inv_inst.rx() - out_pin.lx(),
|
||||
height=out_pin.height())
|
||||
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
a_xoffset = self.logic_inst[0].lx()
|
||||
b_xoffset = self.inv_inst[0].lx()
|
||||
|
|
@ -293,35 +287,35 @@ class bank_select(design.design):
|
|||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
supply_pin = self.inv_inst[num].get_pin(n)
|
||||
supply_offset = supply_pin.ll().scale(0,1)
|
||||
self.add_rect(layer="metal1",
|
||||
supply_offset = supply_pin.ll().scale(0, 1)
|
||||
self.add_rect(layer="m1",
|
||||
offset=supply_offset,
|
||||
width=self.width)
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [a_xoffset, b_xoffset]:
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H","H"))
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
directions=("H", "H"))
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="metal3",
|
||||
layer="m3",
|
||||
offset=pin_pos)
|
||||
|
||||
# Add vdd/gnd supply rails
|
||||
gnd_pin = inv_inst.get_pin("gnd")
|
||||
gnd_pin = self.inv_inst[num].get_pin("gnd")
|
||||
left_gnd_pos = vector(0, gnd_pin.cy())
|
||||
self.add_layout_pin_segment_center(text="gnd",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
start=left_gnd_pos,
|
||||
end=gnd_pin.rc())
|
||||
|
||||
vdd_pin = inv_inst.get_pin("vdd")
|
||||
vdd_pin = self.inv_inst[num].get_pin("vdd")
|
||||
left_vdd_pos = vector(0, vdd_pin.cy())
|
||||
self.add_layout_pin_segment_center(text="vdd",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
start=left_vdd_pos,
|
||||
end=vdd_pin.rc())
|
||||
|
|
|
|||
|
|
@ -5,27 +5,20 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import debug
|
||||
import design
|
||||
from bitcell_base_array import bitcell_base_array
|
||||
from tech import drc, spice
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import logical_effort
|
||||
|
||||
class bitcell_array(design.design):
|
||||
|
||||
class bitcell_array(bitcell_base_array):
|
||||
"""
|
||||
Creates a rows x cols array of memory cells. Assumes bit-lines
|
||||
and word line is connected by abutment.
|
||||
Connects the word lines and bit lines.
|
||||
"""
|
||||
def __init__(self, cols, rows, name):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
||||
|
||||
self.column_size = cols
|
||||
self.row_size = rows
|
||||
def __init__(self, cols, rows, name, column_offset=0):
|
||||
super().__init__(cols, rows, name, column_offset)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -33,8 +26,7 @@ class bitcell_array(design.design):
|
|||
|
||||
# We don't offset this because we need to align
|
||||
# the replica bitcell in the control logic
|
||||
#self.offset_all_coordinates()
|
||||
|
||||
# self.offset_all_coordinates()
|
||||
|
||||
def create_netlist(self):
|
||||
""" Create and connect the netlist """
|
||||
|
|
@ -44,27 +36,7 @@ class bitcell_array(design.design):
|
|||
|
||||
def create_layout(self):
|
||||
|
||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||
self.height = self.row_size*self.cell.height
|
||||
self.width = self.column_size*self.cell.width
|
||||
|
||||
xoffset = 0.0
|
||||
for col in range(self.column_size):
|
||||
yoffset = 0.0
|
||||
for row in range(self.row_size):
|
||||
name = "bit_r{0}_c{1}".format(row, col)
|
||||
|
||||
if row % 2:
|
||||
tempy = yoffset + self.cell.height
|
||||
dir_key = "MX"
|
||||
else:
|
||||
tempy = yoffset
|
||||
dir_key = ""
|
||||
|
||||
self.cell_inst[row,col].place(offset=[xoffset, tempy],
|
||||
mirror=dir_key)
|
||||
yoffset += self.cell.height
|
||||
xoffset += self.cell.width
|
||||
self.place_array("bit_r{0}_c{1}")
|
||||
|
||||
self.add_layout_pins()
|
||||
|
||||
|
|
@ -72,98 +44,35 @@ class bitcell_array(design.design):
|
|||
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
row_list = self.cell.get_all_wl_names()
|
||||
column_list = self.cell.get_all_bitline_names()
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
self.add_pin(cell_column+"_{0}".format(col), "INOUT")
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
self.add_pin(cell_row+"_{0}".format(row), "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Add the modules used in this design """
|
||||
self.cell = factory.create(module_type="bitcell")
|
||||
self.add_mod(self.cell)
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array """
|
||||
|
||||
bitcell_pins = []
|
||||
|
||||
pin_names = self.cell.get_all_bitline_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(col))
|
||||
pin_names = self.cell.get_all_wl_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(row))
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
|
||||
return bitcell_pins
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
""" Create the module instances used in this design """
|
||||
self.cell_inst = {}
|
||||
for col in range(self.column_size):
|
||||
for row in range(self.row_size):
|
||||
name = "bit_r{0}_c{1}".format(row, col)
|
||||
self.cell_inst[row,col]=self.add_inst(name=name,
|
||||
mod=self.cell)
|
||||
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||
mod=self.cell)
|
||||
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add the layout pins """
|
||||
|
||||
row_list = self.cell.get_all_wl_names()
|
||||
column_list = self.cell.get_all_bitline_names()
|
||||
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||
layer=bl_pin.layer,
|
||||
offset=bl_pin.ll().scale(1,0),
|
||||
width=bl_pin.width(),
|
||||
height=self.height)
|
||||
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||
layer=wl_pin.layer,
|
||||
offset=wl_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For every second row and column, add a via for gnd and vdd
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row,col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Power of Bitcell array and bitline in nW."""
|
||||
from tech import drc, parameter
|
||||
|
||||
# Dynamic Power from Bitline
|
||||
bl_wire = self.gen_bl_wire()
|
||||
cell_load = 2 * bl_wire.return_input_cap()
|
||||
cell_load = 2 * bl_wire.return_input_cap()
|
||||
bl_swing = OPTS.rbl_delay_percentage
|
||||
freq = spice["default_event_frequency"]
|
||||
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
||||
|
||||
#Calculate the bitcell power which currently only includes leakage
|
||||
# Calculate the bitcell power which currently only includes leakage
|
||||
cell_power = self.cell.analytical_power(corner, load)
|
||||
|
||||
#Leakage power grows with entire array and bitlines.
|
||||
# Leakage power grows with entire array and bitlines.
|
||||
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
||||
cell_power.leakage * self.column_size * self.row_size)
|
||||
return total_power
|
||||
|
|
@ -173,8 +82,8 @@ class bitcell_array(design.design):
|
|||
width = 0
|
||||
else:
|
||||
width = self.width
|
||||
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1"))
|
||||
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1"))
|
||||
wl_wire.wire_c = 2 * spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||
return wl_wire
|
||||
|
||||
def gen_bl_wire(self):
|
||||
|
|
@ -183,26 +92,26 @@ class bitcell_array(design.design):
|
|||
else:
|
||||
height = self.height
|
||||
bl_pos = 0
|
||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
||||
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
|
||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||
return bl_wire
|
||||
|
||||
def get_wordline_cin(self):
|
||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||
total_cin = bitcell_wl_cin * self.column_size
|
||||
return total_cin
|
||||
|
||||
def graph_exclude_bits(self, targ_row, targ_col):
|
||||
"""Excludes bits in column from being added to graph except target"""
|
||||
#Function is not robust with column mux configurations
|
||||
# Function is not robust with column mux configurations
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
if row == targ_row and col == targ_col:
|
||||
continue
|
||||
self.graph_inst_exclude.add(self.cell_inst[row,col])
|
||||
self.graph_inst_exclude.add(self.cell_inst[row, col])
|
||||
|
||||
def get_cell_name(self, inst_name, row, col):
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col]
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col]
|
||||
|
|
|
|||
|
|
@ -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 design
|
||||
from tech import drc
|
||||
from contact import contact
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class delay_chain(design.design):
|
||||
"""
|
||||
Generate a delay chain with the given number of stages and fanout.
|
||||
|
|
@ -28,7 +27,7 @@ class delay_chain(design.design):
|
|||
|
||||
# Two fanouts are needed so that we can route the vdd/gnd connections
|
||||
for f in fanout_list:
|
||||
debug.check(f>=2,"Must have >=2 fanouts for each stage.")
|
||||
debug.check(f>=2, "Must have >=2 fanouts for each stage.")
|
||||
|
||||
# number of inverters including any fanout loads.
|
||||
self.fanout_list = fanout_list
|
||||
|
|
@ -36,7 +35,6 @@ class delay_chain(design.design):
|
|||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
|
|
@ -45,12 +43,13 @@ class delay_chain(design.design):
|
|||
|
||||
def create_layout(self):
|
||||
# Each stage is a a row
|
||||
self.height = len(self.fanout_list)*self.inv.height
|
||||
self.height = len(self.fanout_list) * self.inv.height
|
||||
# The width is determined by the largest fanout plus the driver
|
||||
self.width = (max(self.fanout_list)+1) * self.inv.width
|
||||
self.width = (max(self.fanout_list) + 1) * self.inv.width
|
||||
|
||||
self.place_inverters()
|
||||
self.route_inverters()
|
||||
self.route_supplies()
|
||||
self.add_layout_pins()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
|
@ -63,15 +62,14 @@ class delay_chain(design.design):
|
|||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
self.inv = factory.create(module_type="pinv", route_output=False)
|
||||
self.inv = factory.create(module_type="pinv")
|
||||
self.add_mod(self.inv)
|
||||
|
||||
def create_inverters(self):
|
||||
""" Create the inverters and connect them based on the stage list """
|
||||
self.driver_inst_list = []
|
||||
self.rightest_load_inst = {}
|
||||
self.load_inst_map = {}
|
||||
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
||||
for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list):
|
||||
# Add the inverter
|
||||
cur_driver=self.add_inst(name="dinv{}".format(stage_num),
|
||||
mod=self.inv)
|
||||
|
|
@ -79,40 +77,37 @@ class delay_chain(design.design):
|
|||
self.driver_inst_list.append(cur_driver)
|
||||
|
||||
# Hook up the driver
|
||||
if stage_num+1==len(self.fanout_list):
|
||||
if stage_num + 1 == len(self.fanout_list):
|
||||
stageout_name = "out"
|
||||
else:
|
||||
stageout_name = "dout_{}".format(stage_num+1)
|
||||
stageout_name = "dout_{}".format(stage_num + 1)
|
||||
if stage_num == 0:
|
||||
stagein_name = "in"
|
||||
else:
|
||||
stagein_name = "dout_{}".format(stage_num)
|
||||
stagein_name = "dout_{}".format(stage_num)
|
||||
self.connect_inst([stagein_name, stageout_name, "vdd", "gnd"])
|
||||
|
||||
# Now add the dummy loads to the right
|
||||
self.load_inst_map[cur_driver]=[]
|
||||
for i in range(fanout_size):
|
||||
cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num,i),
|
||||
cur_load=self.add_inst(name="dload_{0}_{1}".format(stage_num, i),
|
||||
mod=self.inv)
|
||||
# Fanout stage is always driven by driver and output is disconnected
|
||||
disconnect_name = "n_{0}_{1}".format(stage_num,i)
|
||||
disconnect_name = "n_{0}_{1}".format(stage_num, i)
|
||||
self.connect_inst([stageout_name, disconnect_name, "vdd", "gnd"])
|
||||
|
||||
# Keep track of all the loads to connect their inputs as a load
|
||||
self.load_inst_map[cur_driver].append(cur_load)
|
||||
else:
|
||||
# Keep track of the last one so we can add the the wire later
|
||||
self.rightest_load_inst[cur_driver]=cur_load
|
||||
|
||||
def place_inverters(self):
|
||||
""" Place the inverters and connect them based on the stage list """
|
||||
for stage_num,fanout_size in zip(range(len(self.fanout_list)),self.fanout_list):
|
||||
for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list):
|
||||
if stage_num % 2:
|
||||
inv_mirror = "MX"
|
||||
inv_offset = vector(0, (stage_num+1)* self.inv.height)
|
||||
inv_offset = vector(0, (stage_num + 1) * self.inv.height)
|
||||
else:
|
||||
inv_mirror = "R0"
|
||||
inv_offset = vector(0, stage_num * self.inv.height)
|
||||
inv_offset = vector(0, stage_num * self.inv.height)
|
||||
|
||||
# Add the inverter
|
||||
cur_driver=self.driver_inst_list[stage_num]
|
||||
|
|
@ -122,21 +117,20 @@ class delay_chain(design.design):
|
|||
# Now add the dummy loads to the right
|
||||
load_list = self.load_inst_map[cur_driver]
|
||||
for i in range(fanout_size):
|
||||
inv_offset += vector(self.inv.width,0)
|
||||
inv_offset += vector(self.inv.width, 0)
|
||||
load_list[i].place(offset=inv_offset,
|
||||
mirror=inv_mirror)
|
||||
|
||||
|
||||
def add_route(self, pin1, pin2):
|
||||
""" This guarantees that we route from the top to bottom row correctly. """
|
||||
pin1_pos = pin1.center()
|
||||
pin2_pos = pin2.center()
|
||||
if pin1_pos.y == pin2_pos.y:
|
||||
self.add_path("metal2", [pin1_pos, pin2_pos])
|
||||
self.add_path("m2", [pin1_pos, pin2_pos])
|
||||
else:
|
||||
mid_point = vector(pin2_pos.x, 0.5*(pin1_pos.y+pin2_pos.y))
|
||||
mid_point = vector(pin2_pos.x, 0.5 * (pin1_pos.y + pin2_pos.y))
|
||||
# Written this way to guarantee it goes right first if we are switching rows
|
||||
self.add_path("metal2", [pin1_pos, vector(pin1_pos.x,mid_point.y), mid_point, vector(mid_point.x,pin2_pos.y), pin2_pos])
|
||||
self.add_path("m2", [pin1_pos, vector(pin1_pos.x, mid_point.y), mid_point, vector(mid_point.x, pin2_pos.y), pin2_pos])
|
||||
|
||||
def route_inverters(self):
|
||||
""" Add metal routing for each of the fanout stages """
|
||||
|
|
@ -145,100 +139,93 @@ class delay_chain(design.design):
|
|||
inv = self.driver_inst_list[i]
|
||||
for load in self.load_inst_map[inv]:
|
||||
# Drop a via on each A pin
|
||||
a_pin = load.get_pin("A")
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=a_pin.center())
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=a_pin.center())
|
||||
a_pin = load.get_pin("A")
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=a_pin.center())
|
||||
|
||||
# Route an M3 horizontal wire to the furthest
|
||||
z_pin = inv.get_pin("Z")
|
||||
a_pin = inv.get_pin("A")
|
||||
a_max = self.rightest_load_inst[inv].get_pin("A")
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=a_pin.center())
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=z_pin.center())
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=z_pin.center())
|
||||
self.add_path("metal3",[z_pin.center(), a_max.center()])
|
||||
a_max = self.load_inst_map[inv][-1].get_pin("A")
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=z_pin.center())
|
||||
self.add_path("m3", [z_pin.center(), a_max.center()])
|
||||
|
||||
|
||||
# Route Z to the A of the next stage
|
||||
if i+1 < len(self.driver_inst_list):
|
||||
if i + 1 < len(self.driver_inst_list):
|
||||
z_pin = inv.get_pin("Z")
|
||||
next_inv = self.driver_inst_list[i+1]
|
||||
next_inv = self.driver_inst_list[i + 1]
|
||||
next_a_pin = next_inv.get_pin("A")
|
||||
y_mid = (z_pin.cy() + next_a_pin.cy())/2
|
||||
y_mid = (z_pin.cy() + next_a_pin.cy()) / 2
|
||||
mid1_point = vector(z_pin.cx(), y_mid)
|
||||
mid2_point = vector(next_a_pin.cx(), y_mid)
|
||||
self.add_path("metal2",[z_pin.center(), mid1_point, mid2_point, next_a_pin.center()])
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add vdd and gnd rails and the input/output. Connect the gnd rails internally on
|
||||
the top end with no input/output to obstruct. """
|
||||
self.add_path("m2", [z_pin.center(), mid1_point, mid2_point, next_a_pin.center()])
|
||||
|
||||
def route_supplies(self):
|
||||
# Add power and ground to all the cells except:
|
||||
# the fanout driver, the right-most load
|
||||
# The routing to connect the loads is over the first and last cells
|
||||
# We have an even number of drivers and must only do every other
|
||||
# supply rail
|
||||
for i in range(0,len(self.driver_inst_list),2):
|
||||
inv = self.driver_inst_list[i]
|
||||
for load in self.load_inst_map[inv]:
|
||||
if load==self.rightest_load_inst[inv]:
|
||||
continue
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
pin = load.get_pin(pin_name)
|
||||
self.add_power_pin(pin_name, pin.rc())
|
||||
else:
|
||||
# We have an even number of rows, so need to get the last gnd rail
|
||||
inv = self.driver_inst_list[-1]
|
||||
for load in self.load_inst_map[inv]:
|
||||
if load==self.rightest_load_inst[inv]:
|
||||
continue
|
||||
pin_name = "gnd"
|
||||
pin = load.get_pin(pin_name)
|
||||
self.add_power_pin(pin_name, pin.rc())
|
||||
|
||||
for inst in self.driver_inst_list:
|
||||
load_list = self.load_inst_map[inst]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
pin = load_list[0].get_pin(pin_name)
|
||||
self.add_power_pin(pin_name,
|
||||
pin.rc() - vector(self.m1_pitch, 0),
|
||||
start_layer=pin.layer)
|
||||
|
||||
pin = load_list[-2].get_pin(pin_name)
|
||||
self.add_power_pin(pin_name,
|
||||
pin.rc() - vector(self.m1_pitch, 0),
|
||||
start_layer=pin.layer)
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
# input is A pin of first inverter
|
||||
a_pin = self.driver_inst_list[0].get_pin("A")
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
self.add_layout_pin(text="in",
|
||||
layer="metal2",
|
||||
offset=a_pin.ll().scale(1,0),
|
||||
layer="m2",
|
||||
offset=a_pin.ll().scale(1, 0),
|
||||
height=a_pin.cy())
|
||||
|
||||
|
||||
# output is A pin of last load inverter
|
||||
last_driver_inst = self.driver_inst_list[-1]
|
||||
a_pin = self.rightest_load_inst[last_driver_inst].get_pin("A")
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=a_pin.center())
|
||||
mid_point = vector(a_pin.cx()+3*self.m2_width,a_pin.cy())
|
||||
self.add_path("metal2",[a_pin.center(), mid_point, mid_point.scale(1,0)])
|
||||
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy())
|
||||
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)])
|
||||
self.add_layout_pin_segment_center(text="out",
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
start=mid_point,
|
||||
end=mid_point.scale(1,0))
|
||||
end=mid_point.scale(1, 0))
|
||||
|
||||
def get_cin(self):
|
||||
"""Get the enable input ralative capacitance"""
|
||||
#Only 1 input to the delay chain which is connected to an inverter.
|
||||
# Only 1 input to the delay chain which is connected to an inverter.
|
||||
dc_cin = self.inv.get_cin()
|
||||
return dc_cin
|
||||
return dc_cin
|
||||
|
||||
def determine_delayed_en_stage_efforts(self, ext_delayed_en_cout, inp_is_rise=True):
|
||||
"""Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load."""
|
||||
stage_effort_list = []
|
||||
#Add a stage to the list for every stage in delay chain. Stages only differ in fanout except the last which has an external cout.
|
||||
# Add a stage to the list for every stage in delay chain.
|
||||
# Stages only differ in fanout except the last which has an external cout.
|
||||
last_stage_is_rise = inp_is_rise
|
||||
for stage_fanout in self.fanout_list:
|
||||
stage_cout = self.inv.get_cin()*(stage_fanout+1)
|
||||
if len(stage_effort_list) == len(self.fanout_list)-1: #last stage
|
||||
stage_cout = self.inv.get_cin() * (stage_fanout + 1)
|
||||
if len(stage_effort_list) == len(self.fanout_list) - 1:
|
||||
stage_cout+=ext_delayed_en_cout
|
||||
stage = self.inv.get_stage_effort(stage_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
from math import log
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class dff_array(design.design):
|
||||
"""
|
||||
This is a simple row (or multiple rows) of flops.
|
||||
|
|
@ -52,41 +51,41 @@ class dff_array(design.design):
|
|||
self.add_mod(self.dff)
|
||||
|
||||
def add_pins(self):
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_din_name(row,col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
self.add_pin(self.get_din_name(row, col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_dout_name(row,col), "OUTPUT")
|
||||
self.add_pin(self.get_dout_name(row, col), "OUTPUT")
|
||||
self.add_pin("clk", "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def create_dff_array(self):
|
||||
self.dff_insts={}
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
self.connect_inst([self.get_din_name(row,col),
|
||||
self.get_dout_name(row,col),
|
||||
"clk",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
name = "dff_r{0}_c{1}".format(row, col)
|
||||
self.dff_insts[row, col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
instance_ports = [self.get_din_name(row, col),
|
||||
self.get_dout_name(row, col)]
|
||||
for port in self.dff.pin_names:
|
||||
if port != 'D' and port != 'Q':
|
||||
instance_ports.append(port)
|
||||
self.connect_inst(instance_ports)
|
||||
|
||||
def place_dff_array(self):
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
if (row % 2 == 0):
|
||||
base = vector(col*self.dff.width,row*self.dff.height)
|
||||
base = vector(col * self.dff.width, row * self.dff.height)
|
||||
mirror = "R0"
|
||||
else:
|
||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||
base = vector(col * self.dff.width, (row + 1) * self.dff.height)
|
||||
mirror = "MX"
|
||||
self.dff_insts[row,col].place(offset=base,
|
||||
mirror=mirror)
|
||||
self.dff_insts[row, col].place(offset=base,
|
||||
mirror=mirror)
|
||||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
|
|
@ -94,7 +93,7 @@ class dff_array(design.design):
|
|||
elif self.rows == 1:
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row, col)
|
||||
|
||||
return din_name
|
||||
|
||||
|
|
@ -104,61 +103,58 @@ class dff_array(design.design):
|
|||
elif self.rows == 1:
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row, col)
|
||||
|
||||
return dout_name
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
for col in range(self.columns):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.center())
|
||||
vdd_pin=self.dff_insts[row, col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer)
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.center())
|
||||
gnd_pin=self.dff_insts[row, col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer)
|
||||
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row, col].get_pin("D")
|
||||
debug.check(din_pin.layer == "m2", "DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row, col),
|
||||
layer=din_pin.layer,
|
||||
offset=din_pin.ll(),
|
||||
width=din_pin.width(),
|
||||
height=din_pin.height())
|
||||
|
||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
||||
dout_pin = self.dff_insts[row, col].get_pin("Q")
|
||||
debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row, col),
|
||||
layer=dout_pin.layer,
|
||||
offset=dout_pin.ll(),
|
||||
width=dout_pin.width(),
|
||||
height=dout_pin.height())
|
||||
|
||||
|
||||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin)
|
||||
clk_ypos = 2 * self.m3_pitch + self.m3_width
|
||||
debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2")
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
layer="m3",
|
||||
start=vector(0, clk_ypos),
|
||||
end=vector(self.width, clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin)
|
||||
# Make a vertical strip for each column
|
||||
self.add_rect(layer="metal2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
self.add_rect(layer="m2",
|
||||
offset=clk_pin.ll().scale(1, 0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=vector(clk_pin.cx(), clk_ypos))
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc,parameter
|
||||
from math import log
|
||||
from tech import parameter, layer
|
||||
from tech import cell_properties as props
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class dff_buf(design.design):
|
||||
"""
|
||||
This is a simple buffered DFF. The output is buffered
|
||||
|
|
@ -34,7 +35,7 @@ class dff_buf(design.design):
|
|||
# This causes a DRC in the pinv which assumes min width rails. This ensures the output
|
||||
# contact does not violate spacing to the rail in the NMOS.
|
||||
debug.check(inv1_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||
debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||
debug.check(inv2_size>=2, "Inverter must be greater than two for rail spacing DRC rules.")
|
||||
|
||||
self.inv1_size=inv1_size
|
||||
self.inv2_size=inv2_size
|
||||
|
|
@ -49,15 +50,14 @@ class dff_buf(design.design):
|
|||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
self.width = self.dff.width + self.inv1.width + self.inv2.width
|
||||
self.height = self.dff.height
|
||||
|
||||
self.place_instances()
|
||||
self.width = self.inv2_inst.rx()
|
||||
self.height = self.dff.height
|
||||
self.route_wires()
|
||||
self.add_layout_pins()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
self.dff = factory.create(module_type="dff")
|
||||
self.add_mod(self.dff)
|
||||
|
|
@ -71,8 +71,6 @@ class dff_buf(design.design):
|
|||
size=self.inv2_size,
|
||||
height=self.dff.height)
|
||||
self.add_mod(self.inv2)
|
||||
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
self.add_pin("D", "INPUT")
|
||||
|
|
@ -82,58 +80,76 @@ class dff_buf(design.design):
|
|||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
if props.dff_buff.add_body_contacts:
|
||||
self.add_pin("vpb", "INPUT")
|
||||
self.add_pin("vpn", "INPUT")
|
||||
|
||||
def create_instances(self):
|
||||
self.dff_inst=self.add_inst(name="dff_buf_dff",
|
||||
mod=self.dff)
|
||||
|
||||
self.connect_inst(["D", "qint", "clk", "vdd", "gnd"])
|
||||
|
||||
self.inv1_inst=self.add_inst(name="dff_buf_inv1",
|
||||
mod=self.inv1)
|
||||
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
||||
self.connect_inst(["qint", "Qb", "vdd", "gnd"])
|
||||
|
||||
self.inv2_inst=self.add_inst(name="dff_buf_inv2",
|
||||
mod=self.inv2)
|
||||
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
||||
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
||||
|
||||
def place_instances(self):
|
||||
# Add the DFF
|
||||
self.dff_inst.place(vector(0,0))
|
||||
self.dff_inst.place(vector(0, 0))
|
||||
|
||||
# Add INV1 to the right
|
||||
self.inv1_inst.place(vector(self.dff_inst.rx(),0))
|
||||
|
||||
# The INV needs well spacing because the DFF is likely from a library
|
||||
# with different well construction rules
|
||||
well_spacing = 0
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.nwell_space)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.pwell_space)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.pwell_to_nwell)
|
||||
except AttributeError:
|
||||
pass
|
||||
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0))
|
||||
|
||||
# Add INV2 to the right
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(), 0))
|
||||
|
||||
def route_wires(self):
|
||||
if "li" in layer:
|
||||
self.route_layer = "li"
|
||||
else:
|
||||
self.route_layer = "m1"
|
||||
|
||||
# Route dff q to inv1 a
|
||||
q_pin = self.dff_inst.get_pin("Q")
|
||||
a1_pin = self.inv1_inst.get_pin("A")
|
||||
mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx())
|
||||
mid1 = vector(mid_x_offset, q_pin.cy())
|
||||
mid2 = vector(mid_x_offset, a1_pin.cy())
|
||||
self.add_path("metal3", [q_pin.center(), mid1, mid2, a1_pin.center()])
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=q_pin.center())
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=a1_pin.center())
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=a1_pin.center())
|
||||
mid1 = vector(a1_pin.cx(), q_pin.cy())
|
||||
self.add_path(q_pin.layer, [q_pin.center(), mid1, a1_pin.center()], width=q_pin.height())
|
||||
self.add_via_stack_center(from_layer=a1_pin.layer,
|
||||
to_layer=q_pin.layer,
|
||||
offset=a1_pin.center())
|
||||
|
||||
# Route inv1 z to inv2 a
|
||||
z1_pin = self.inv1_inst.get_pin("Z")
|
||||
a2_pin = self.inv2_inst.get_pin("A")
|
||||
mid_x_offset = 0.5*(z1_pin.cx() + a2_pin.cx())
|
||||
self.mid_qb_pos = vector(mid_x_offset, z1_pin.cy())
|
||||
mid2 = vector(mid_x_offset, a2_pin.cy())
|
||||
self.add_path("metal1", [z1_pin.center(), self.mid_qb_pos, mid2, a2_pin.center()])
|
||||
self.mid_qb_pos = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy())
|
||||
self.add_zjog(z1_pin.layer, z1_pin.center(), a2_pin.center())
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_inst.get_pin("vdd")
|
||||
self.add_layout_pin(text="vdd",
|
||||
layer="metal1",
|
||||
layer=vdd_pin.layer,
|
||||
offset=vdd_pin.ll(),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
|
@ -141,7 +157,7 @@ class dff_buf(design.design):
|
|||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_inst.get_pin("gnd")
|
||||
self.add_layout_pin(text="gnd",
|
||||
layer="metal1",
|
||||
layer=gnd_pin.layer,
|
||||
offset=gnd_pin.ll(),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
|
@ -161,26 +177,29 @@ class dff_buf(design.design):
|
|||
height=din_pin.height())
|
||||
|
||||
dout_pin = self.inv2_inst.get_pin("Z")
|
||||
mid_pos = dout_pin.center() + vector(self.m1_pitch,0)
|
||||
q_pos = mid_pos - vector(0,self.m2_pitch)
|
||||
mid_pos = dout_pin.center() + vector(self.m2_nonpref_pitch, 0)
|
||||
q_pos = mid_pos - vector(0, 2 * self.m2_nonpref_pitch)
|
||||
self.add_layout_pin_rect_center(text="Q",
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=q_pos)
|
||||
self.add_path("metal1", [dout_pin.center(), mid_pos, q_pos])
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=q_pos)
|
||||
self.add_path(self.route_layer, [dout_pin.center(), mid_pos, q_pos])
|
||||
self.add_via_stack_center(from_layer=dout_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=q_pos)
|
||||
|
||||
qb_pos = self.mid_qb_pos + vector(0,self.m2_pitch)
|
||||
qb_pos = self.mid_qb_pos + vector(0, 2 * self.m2_nonpref_pitch)
|
||||
self.add_layout_pin_rect_center(text="Qb",
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=qb_pos)
|
||||
self.add_path("metal1", [self.mid_qb_pos, qb_pos])
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=qb_pos)
|
||||
self.add_path(self.route_layer, [self.mid_qb_pos, qb_pos])
|
||||
a2_pin = self.inv2_inst.get_pin("A")
|
||||
self.add_via_stack_center(from_layer=a2_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=qb_pos)
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff"""
|
||||
#This is a handmade cell so the value must be entered in the tech.py file or estimated.
|
||||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||
#FIXME: Dff changed in a past commit. The parameter need to be updated.
|
||||
# This is a handmade cell so the value must be entered in the tech.py file or estimated.
|
||||
# Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||
# FIXME: Dff changed in a past commit. The parameter need to be updated.
|
||||
return parameter["dff_clk_cin"]
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
from math import log
|
||||
from tech import cell_properties as props
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class dff_buf_array(design.design):
|
||||
"""
|
||||
This is a simple row (or multiple rows) of flops.
|
||||
|
|
@ -48,22 +48,27 @@ class dff_buf_array(design.design):
|
|||
self.width = self.columns * self.dff.width
|
||||
self.height = self.rows * self.dff.height
|
||||
self.place_dff_array()
|
||||
self.route_supplies()
|
||||
self.add_layout_pins()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_din_name(row,col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
self.add_pin(self.get_din_name(row, col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_dout_name(row,col), "OUTPUT")
|
||||
self.add_pin(self.get_dout_bar_name(row,col), "OUTPUT")
|
||||
self.add_pin(self.get_dout_name(row, col), "OUTPUT")
|
||||
self.add_pin(self.get_dout_bar_name(row, col), "OUTPUT")
|
||||
self.add_pin("clk", "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
if props.dff_buff_array.add_body_contacts:
|
||||
self.add_pin("vpb", "INPUT")
|
||||
self.add_pin("vnb", "INPUT")
|
||||
|
||||
def add_modules(self):
|
||||
self.dff = factory.create(module_type="dff_buf",
|
||||
inv1_size=self.inv1_size,
|
||||
|
|
@ -72,30 +77,51 @@ class dff_buf_array(design.design):
|
|||
|
||||
def create_dff_array(self):
|
||||
self.dff_insts={}
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
self.connect_inst([self.get_din_name(row,col),
|
||||
self.get_dout_name(row,col),
|
||||
self.get_dout_bar_name(row,col),
|
||||
name = "dff_r{0}_c{1}".format(row, col)
|
||||
self.dff_insts[row, col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
inst_ports = [self.get_din_name(row, col),
|
||||
self.get_dout_name(row, col),
|
||||
self.get_dout_bar_name(row, col),
|
||||
"clk",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
"gnd"]
|
||||
if props.dff_buff_array.add_body_contacts:
|
||||
inst_ports.append("vpb")
|
||||
inst_ports.append("vnb")
|
||||
self.connect_inst(inst_ports)
|
||||
|
||||
def place_dff_array(self):
|
||||
for row in range(self.rows):
|
||||
|
||||
well_spacing = 0
|
||||
try:
|
||||
well_spacing = max(self.nwell_space, well_spacing)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(self.pwell_space, well_spacing)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(self.pwell_to_nwell, well_spacing)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
dff_pitch = self.dff.width + well_spacing + self.well_extend_active
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||
# name = "Xdff_r{0}_c{1}".format(row, col)
|
||||
if (row % 2 == 0):
|
||||
base = vector(col*self.dff.width,row*self.dff.height)
|
||||
base = vector(col * dff_pitch, row * self.dff.height)
|
||||
mirror = "R0"
|
||||
else:
|
||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||
base = vector(col * dff_pitch, (row + 1) * self.dff.height)
|
||||
mirror = "MX"
|
||||
self.dff_insts[row,col].place(offset=base,
|
||||
mirror=mirror)
|
||||
self.dff_insts[row, col].place(offset=base,
|
||||
mirror=mirror)
|
||||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
|
|
@ -103,7 +129,7 @@ class dff_buf_array(design.design):
|
|||
elif self.rows == 1:
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row, col)
|
||||
|
||||
return din_name
|
||||
|
||||
|
|
@ -113,7 +139,7 @@ class dff_buf_array(design.design):
|
|||
elif self.rows == 1:
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row, col)
|
||||
|
||||
return dout_name
|
||||
|
||||
|
|
@ -123,75 +149,84 @@ class dff_buf_array(design.design):
|
|||
elif self.rows == 1:
|
||||
dout_bar_name = "dout_bar_{0}".format(col)
|
||||
else:
|
||||
dout_bar_name = "dout_bar_{0}_{1}".format(row,col)
|
||||
dout_bar_name = "dout_bar_{0}_{1}".format(row, col)
|
||||
|
||||
return dout_bar_name
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
def route_supplies(self):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
vdd0_pin=self.dff_insts[row, 0].get_pin("vdd")
|
||||
vddn_pin=self.dff_insts[row, self.columns - 1].get_pin("vdd")
|
||||
self.add_path(vdd0_pin.layer, [vdd0_pin.lc(), vddn_pin.rc()], width=vdd0_pin.height())
|
||||
|
||||
gnd0_pin=self.dff_insts[row, 0].get_pin("gnd")
|
||||
gndn_pin=self.dff_insts[row, self.columns - 1].get_pin("gnd")
|
||||
self.add_path(gnd0_pin.layer, [gnd0_pin.lc(), gndn_pin.rc()], width=gnd0_pin.height())
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.lc())
|
||||
vdd_pin=self.dff_insts[row, col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer)
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.lc())
|
||||
gnd_pin=self.dff_insts[row, col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer)
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row, col].get_pin("D")
|
||||
debug.check(din_pin.layer=="m2", "DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row, col),
|
||||
layer=din_pin.layer,
|
||||
offset=din_pin.ll(),
|
||||
width=din_pin.width(),
|
||||
height=din_pin.height())
|
||||
|
||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
||||
dout_pin = self.dff_insts[row, col].get_pin("Q")
|
||||
debug.check(dout_pin.layer=="m2", "DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row, col),
|
||||
layer=dout_pin.layer,
|
||||
offset=dout_pin.ll(),
|
||||
width=dout_pin.width(),
|
||||
height=dout_pin.height())
|
||||
|
||||
dout_bar_pin = self.dff_insts[row,col].get_pin("Qb")
|
||||
debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_bar_name(row,col),
|
||||
dout_bar_pin = self.dff_insts[row, col].get_pin("Qb")
|
||||
debug.check(dout_bar_pin.layer=="m2", "DFF Qb pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_bar_name(row, col),
|
||||
layer=dout_bar_pin.layer,
|
||||
offset=dout_bar_pin.ll(),
|
||||
width=dout_bar_pin.width(),
|
||||
height=dout_bar_pin.height())
|
||||
|
||||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
clk_pin = self.dff_insts[0, 0].get_pin("clk")
|
||||
clk_ypos = 2 * self.m3_pitch + self.m3_width
|
||||
debug.check(clk_pin.layer=="m2", "DFF clk pin not on metal2")
|
||||
if self.columns==1:
|
||||
self.add_layout_pin(text="clk",
|
||||
layer="metal2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
layer="m2",
|
||||
offset=clk_pin.ll().scale(1, 0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
else:
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
layer="m3",
|
||||
start=vector(0, clk_ypos),
|
||||
end=vector(self.width, clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
clk_pin = self.dff_insts[0, col].get_pin("clk")
|
||||
|
||||
# Make a vertical strip for each column
|
||||
self.add_rect(layer="metal2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
self.add_rect(layer="m2",
|
||||
offset=clk_pin.ll().scale(1, 0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=vector(clk_pin.cx(), clk_ypos))
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
|
|
|
|||
|
|
@ -97,13 +97,13 @@ class dff_inv(design.design):
|
|||
mid_x_offset = 0.5*(a1_pin.cx() + q_pin.cx())
|
||||
mid1 = vector(mid_x_offset, q_pin.cy())
|
||||
mid2 = vector(mid_x_offset, a1_pin.cy())
|
||||
self.add_path("metal3",
|
||||
self.add_path("m3",
|
||||
[q_pin.center(), mid1, mid2, a1_pin.center()])
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=q_pin.center())
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=a1_pin.center())
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=a1_pin.center())
|
||||
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ class dff_inv(design.design):
|
|||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_inst.get_pin("vdd")
|
||||
self.add_layout_pin(text="vdd",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=vdd_pin.ll(),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
|
@ -120,7 +120,7 @@ class dff_inv(design.design):
|
|||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_inst.get_pin("gnd")
|
||||
self.add_layout_pin(text="gnd",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=gnd_pin.ll(),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
|
@ -146,9 +146,9 @@ class dff_inv(design.design):
|
|||
|
||||
dout_pin = self.inv1_inst.get_pin("Z")
|
||||
self.add_layout_pin_rect_center(text="Qb",
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=dout_pin.center())
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=dout_pin.center())
|
||||
|
||||
def get_clk_cin(self):
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class dff_inv_array(design.design):
|
|||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
||||
debug.check(din_pin.layer=="metal2","DFF D pin not on metal2")
|
||||
debug.check(din_pin.layer=="m2","DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
||||
layer=din_pin.layer,
|
||||
offset=din_pin.ll(),
|
||||
|
|
@ -148,7 +148,7 @@ class dff_inv_array(design.design):
|
|||
height=din_pin.height())
|
||||
|
||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
||||
debug.check(dout_pin.layer=="metal2","DFF Q pin not on metal2")
|
||||
debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
||||
layer=dout_pin.layer,
|
||||
offset=dout_pin.ll(),
|
||||
|
|
@ -156,7 +156,7 @@ class dff_inv_array(design.design):
|
|||
height=dout_pin.height())
|
||||
|
||||
dout_bar_pin = self.dff_insts[row,col].get_pin("Qb")
|
||||
debug.check(dout_bar_pin.layer=="metal2","DFF Qb pin not on metal2")
|
||||
debug.check(dout_bar_pin.layer=="m2","DFF Qb pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_bar_name(row,col),
|
||||
layer=dout_bar_pin.layer,
|
||||
offset=dout_bar_pin.ll(),
|
||||
|
|
@ -167,27 +167,27 @@ class dff_inv_array(design.design):
|
|||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2")
|
||||
if self.columns==1:
|
||||
self.add_layout_pin(text="clk",
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
else:
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
layer="m3",
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
# Make a vertical strip for each column
|
||||
self.add_rect(layer="metal2",
|
||||
self.add_rect(layer="m2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
|
||||
def get_clk_cin(self):
|
||||
|
|
|
|||
|
|
@ -3,31 +3,22 @@
|
|||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
import contact
|
||||
from bitcell_base_array import bitcell_base_array
|
||||
from sram_factory import factory
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
||||
class dummy_array(design.design):
|
||||
|
||||
class dummy_array(bitcell_base_array):
|
||||
"""
|
||||
Generate a dummy row/column for the replica array.
|
||||
"""
|
||||
def __init__(self, cols, rows, mirror=0, name=""):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
||||
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
|
||||
|
||||
self.column_size = cols
|
||||
self.row_size = rows
|
||||
def __init__(self, cols, rows, column_offset=0, mirror=0, name=""):
|
||||
super().__init__(cols, rows, name, column_offset)
|
||||
self.mirror = mirror
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
""" Create and connect the netlist """
|
||||
|
|
@ -37,27 +28,7 @@ class dummy_array(design.design):
|
|||
|
||||
def create_layout(self):
|
||||
|
||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||
self.height = self.row_size*self.dummy_cell.height
|
||||
self.width = self.column_size*self.dummy_cell.width
|
||||
|
||||
xoffset = 0.0
|
||||
for col in range(self.column_size):
|
||||
yoffset = 0.0
|
||||
for row in range(self.row_size):
|
||||
name = "dummy_r{0}_c{1}".format(row, col)
|
||||
|
||||
if (row+self.mirror) % 2:
|
||||
tempy = yoffset + self.dummy_cell.height
|
||||
dir_key = "MX"
|
||||
else:
|
||||
tempy = yoffset
|
||||
dir_key = ""
|
||||
|
||||
self.cell_inst[row,col].place(offset=[xoffset, tempy],
|
||||
mirror=dir_key)
|
||||
yoffset += self.dummy_cell.height
|
||||
xoffset += self.dummy_cell.width
|
||||
self.place_array("dummy_r{0}_c{1}", self.mirror)
|
||||
|
||||
self.add_layout_pins()
|
||||
|
||||
|
|
@ -65,85 +36,22 @@ class dummy_array(design.design):
|
|||
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
row_list = self.cell.get_all_wl_names()
|
||||
column_list = self.cell.get_all_bitline_names()
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
self.add_pin(cell_column+"_{0}".format(col), "INOUT")
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
self.add_pin(cell_row+"_{0}".format(row), "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Add the modules used in this design """
|
||||
self.dummy_cell = factory.create(module_type="dummy_bitcell")
|
||||
self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
|
||||
self.add_mod(self.dummy_cell)
|
||||
|
||||
self.cell = factory.create(module_type="bitcell")
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array """
|
||||
|
||||
bitcell_pins = []
|
||||
|
||||
pin_names = self.cell.get_all_bitline_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(col))
|
||||
pin_names = self.cell.get_all_wl_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(row))
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
|
||||
return bitcell_pins
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
""" Create the module instances used in this design """
|
||||
self.cell_inst = {}
|
||||
for col in range(self.column_size):
|
||||
for row in range(self.row_size):
|
||||
name = "bit_r{0}_c{1}".format(row, col)
|
||||
self.cell_inst[row,col]=self.add_inst(name=name,
|
||||
mod=self.dummy_cell)
|
||||
self.cell_inst[row, col]=self.add_inst(name=name,
|
||||
mod=self.dummy_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(col, row))
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add the layout pins """
|
||||
|
||||
row_list = self.cell.get_all_wl_names()
|
||||
column_list = self.cell.get_all_bitline_names()
|
||||
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=bl_pin.width(),
|
||||
height=self.height)
|
||||
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||
layer="metal1",
|
||||
offset=wl_pin.ll(),
|
||||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For every second row and column, add a via for gnd and vdd
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row,col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
||||
|
||||
|
||||
def input_load(self):
|
||||
wl_wire = self.gen_wl_wire()
|
||||
|
|
@ -151,7 +59,7 @@ class dummy_array(design.design):
|
|||
|
||||
def get_wordline_cin(self):
|
||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||
total_cin = bitcell_wl_cin * self.column_size
|
||||
return total_cin
|
||||
|
|
|
|||
|
|
@ -5,40 +5,37 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import design
|
||||
from math import log
|
||||
from math import sqrt
|
||||
import math
|
||||
import contact
|
||||
from sram_factory import factory
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class hierarchical_decoder(design.design):
|
||||
"""
|
||||
Dynamically generated hierarchical decoder.
|
||||
"""
|
||||
def __init__(self, name, rows, height=None):
|
||||
def __init__(self, name, num_outputs):
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.NAND_FORMAT = "DEC_NAND_{0}"
|
||||
self.INV_FORMAT = "DEC_INV_{0}"
|
||||
self.AND_FORMAT = "DEC_AND_{0}"
|
||||
|
||||
self.pre2x4_inst = []
|
||||
self.pre3x8_inst = []
|
||||
|
||||
self.cell_height = height
|
||||
self.rows = rows
|
||||
self.num_inputs = int(math.log(self.rows, 2))
|
||||
(self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||
b = factory.create(module_type="bitcell")
|
||||
self.cell_height = b.height
|
||||
|
||||
self.num_outputs = num_outputs
|
||||
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
|
||||
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
self.setup_netlist_constants()
|
||||
|
|
@ -50,23 +47,32 @@ class hierarchical_decoder(design.design):
|
|||
self.setup_layout_constants()
|
||||
self.place_pre_decoder()
|
||||
self.place_row_decoder()
|
||||
self.route_input_rails()
|
||||
self.route_predecode_rails()
|
||||
|
||||
self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space
|
||||
|
||||
self.route_inputs()
|
||||
self.route_outputs()
|
||||
self.route_decoder_bus()
|
||||
self.route_vdd_gnd()
|
||||
|
||||
self.offset_all_coordinates()
|
||||
|
||||
self.width = self.and_inst[0].rx() + self.m1_space
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_modules(self):
|
||||
self.inv = factory.create(module_type="pinv",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.inv)
|
||||
self.nand2 = factory.create(module_type="pnand2",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.nand2)
|
||||
self.nand3 = factory.create(module_type="pnand3",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.nand3)
|
||||
self.and2 = factory.create(module_type="and2_dec",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.and2)
|
||||
|
||||
self.and3 = factory.create(module_type="and3_dec",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.and3)
|
||||
# TBD
|
||||
# self.and4 = factory.create(module_type="and4_dec")
|
||||
# self.add_mod(self.and4)
|
||||
|
||||
self.add_decoders()
|
||||
|
||||
|
|
@ -80,32 +86,32 @@ class hierarchical_decoder(design.design):
|
|||
height=self.cell_height)
|
||||
self.add_mod(self.pre3_8)
|
||||
|
||||
def determine_predecodes(self,num_inputs):
|
||||
def determine_predecodes(self, num_inputs):
|
||||
""" Determines the number of 2:4 pre-decoder and 3:8 pre-decoder
|
||||
needed based on the number of inputs """
|
||||
if (num_inputs == 2):
|
||||
return (1,0)
|
||||
return (1, 0)
|
||||
elif (num_inputs == 3):
|
||||
return(0,1)
|
||||
return(0, 1)
|
||||
elif (num_inputs == 4):
|
||||
return(2,0)
|
||||
return(2, 0)
|
||||
elif (num_inputs == 5):
|
||||
return(1,1)
|
||||
return(1, 1)
|
||||
elif (num_inputs == 6):
|
||||
return(3,0)
|
||||
return(3, 0)
|
||||
elif (num_inputs == 7):
|
||||
return(2,1)
|
||||
return(2, 1)
|
||||
elif (num_inputs == 8):
|
||||
return(1,2)
|
||||
return(1, 2)
|
||||
elif (num_inputs == 9):
|
||||
return(0,3)
|
||||
return(0, 3)
|
||||
else:
|
||||
debug.error("Invalid number of inputs for hierarchical decoder",-1)
|
||||
debug.error("Invalid number of inputs for hierarchical decoder", -1)
|
||||
|
||||
def setup_netlist_constants(self):
|
||||
self.predec_groups = [] # This array is a 2D array.
|
||||
|
||||
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
|
||||
# Distributing vertical bus to different groups. One group belongs to one pre-decoder.
|
||||
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
|
||||
# have total 16 output lines out of these 3 pre-decoders and they will
|
||||
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||
|
|
@ -125,107 +131,121 @@ class hierarchical_decoder(design.design):
|
|||
index = index + 1
|
||||
self.predec_groups.append(lines)
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
""" Calculate the overall dimensions of the hierarchical decoder """
|
||||
|
||||
# If we have 4 or fewer rows, the predecoder is the decoder itself
|
||||
if self.num_inputs>=4:
|
||||
self.total_number_of_predecoder_outputs = 4*self.no_of_pre2x4 + 8*self.no_of_pre3x8
|
||||
self.total_number_of_predecoder_outputs = 4 * self.no_of_pre2x4 + 8 * self.no_of_pre3x8
|
||||
else:
|
||||
self.total_number_of_predecoder_outputs = 0
|
||||
debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs),-1)
|
||||
self.total_number_of_predecoder_outputs = 0
|
||||
debug.error("Not enough rows ({}) for a hierarchical decoder. Non-hierarchical not supported yet.".format(self.num_inputs),
|
||||
-1)
|
||||
|
||||
# Calculates height and width of pre-decoder,
|
||||
if self.no_of_pre3x8 > 0:
|
||||
self.predecoder_width = self.pre3_8.width
|
||||
# FIXME: Update with 4x16
|
||||
if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0:
|
||||
self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width)
|
||||
elif self.no_of_pre3x8 > 0:
|
||||
self.predecoder_width = self.pre3_8.width
|
||||
else:
|
||||
self.predecoder_width = self.pre2_4.width
|
||||
|
||||
self.predecoder_height = self.pre2_4.height*self.no_of_pre2x4 + self.pre3_8.height*self.no_of_pre3x8
|
||||
|
||||
# Calculates height and width of row-decoder
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
nand_width = self.nand2.width
|
||||
# How much space between each predecoder
|
||||
self.predecoder_spacing = 2 * self.and2.height
|
||||
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \
|
||||
+ (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing
|
||||
|
||||
# Inputs to cells are on input layer
|
||||
# Outputs from cells are on output layer
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.bus_layer = "m1"
|
||||
self.bus_directions = "nonpref"
|
||||
self.bus_pitch = self.m1_pitch
|
||||
self.bus_space = self.m2_space
|
||||
self.input_layer = "m2"
|
||||
self.output_layer = "li"
|
||||
self.output_layer_pitch = self.li_pitch
|
||||
else:
|
||||
nand_width = self.nand3.width
|
||||
self.internal_routing_width = self.m2_pitch*self.total_number_of_predecoder_outputs
|
||||
self.row_decoder_height = self.inv.height * self.rows
|
||||
self.bus_layer = "m2"
|
||||
self.bus_directions = "pref"
|
||||
self.bus_pitch = self.m2_pitch
|
||||
self.bus_space = self.m2_space
|
||||
# These two layers being the same requires a special jog
|
||||
# to ensure to conflicts with the output layers
|
||||
self.input_layer = "m1"
|
||||
self.output_layer = "m3"
|
||||
self.output_layer_pitch = self.m3_pitch
|
||||
|
||||
self.input_routing_width = (self.num_inputs+1) * self.m2_pitch
|
||||
# Calculates height and width of hierarchical decoder
|
||||
self.height = self.row_decoder_height
|
||||
self.width = self.input_routing_width + self.predecoder_width \
|
||||
+ self.internal_routing_width + nand_width + self.inv.width
|
||||
|
||||
def route_input_rails(self):
|
||||
""" Create input rails for the predecoders """
|
||||
# inputs should be as high as the decoders
|
||||
input_height = self.no_of_pre2x4*self.pre2_4.height + self.no_of_pre3x8*self.pre3_8.height
|
||||
# Two extra pitches between modules on left and right
|
||||
self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch
|
||||
self.row_decoder_height = self.and2.height * self.num_outputs
|
||||
|
||||
# Extra bus space for supply contacts
|
||||
self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space
|
||||
|
||||
def route_inputs(self):
|
||||
""" Create input bus for the predecoders """
|
||||
# Find the left-most predecoder
|
||||
min_x = 0
|
||||
if self.no_of_pre2x4 > 0:
|
||||
min_x = min(min_x, -self.pre2_4.width)
|
||||
min_x = min(min_x, self.pre2x4_inst[0].lx())
|
||||
if self.no_of_pre3x8 > 0:
|
||||
min_x = min(min_x, -self.pre3_8.width)
|
||||
input_offset=vector(min_x - self.input_routing_width,0)
|
||||
min_x = min(min_x, self.pre3x8_inst[0].lx())
|
||||
input_offset=vector(min_x - self.input_routing_width, 0)
|
||||
|
||||
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=input_height)
|
||||
self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=self.predecoder_height)
|
||||
|
||||
self.route_input_to_predecodes()
|
||||
|
||||
|
||||
def route_input_to_predecodes(self):
|
||||
""" Route the vertical input rail to the predecoders """
|
||||
for pre_num in range(self.no_of_pre2x4):
|
||||
for i in range(2):
|
||||
index = pre_num * 2 + i
|
||||
|
||||
input_pos = self.input_rails["addr_{}".format(index)]
|
||||
input_pos = self.input_bus["addr_{}".format(index)].center()
|
||||
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||
|
||||
# To prevent conflicts, we will offset each input connect so
|
||||
# that it aligns with the vdd/gnd rails
|
||||
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
||||
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
||||
decoder_offset = decoder_pin.center()
|
||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||
|
||||
self.route_input_rail(decoder_offset, input_offset)
|
||||
|
||||
self.route_input_bus(decoder_offset, input_offset)
|
||||
|
||||
for pre_num in range(self.no_of_pre3x8):
|
||||
for i in range(3):
|
||||
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
||||
|
||||
input_pos = self.input_rails["addr_{}".format(index)]
|
||||
input_pos = self.input_bus["addr_{}".format(index)].center()
|
||||
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||
|
||||
# To prevent conflicts, we will offset each input connect so
|
||||
# that it aligns with the vdd/gnd rails
|
||||
decoder_offset = decoder_pin.bc() + vector(0,(i+1)*self.inv.height)
|
||||
input_offset = input_pos.scale(1,0) + decoder_offset.scale(0,1)
|
||||
decoder_offset = decoder_pin.center()
|
||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||
|
||||
self.route_input_rail(decoder_offset, input_offset)
|
||||
|
||||
self.route_input_bus(decoder_offset, input_offset)
|
||||
|
||||
def route_input_rail(self, input_offset, output_offset):
|
||||
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
|
||||
def route_input_bus(self, input_offset, output_offset):
|
||||
"""
|
||||
Route a vertical M2 coordinate to another
|
||||
vertical M2 coordinate to the predecode inputs
|
||||
"""
|
||||
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=input_offset)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=output_offset)
|
||||
self.add_path(("metal3"), [input_offset, output_offset])
|
||||
|
||||
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=input_offset)
|
||||
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=output_offset,
|
||||
directions=self.bus_directions)
|
||||
self.add_path(self.input_layer, [input_offset, output_offset])
|
||||
|
||||
def add_pins(self):
|
||||
""" Add the module pins """
|
||||
|
|
@ -233,12 +253,11 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(self.num_inputs):
|
||||
self.add_pin("addr_{0}".format(i), "INPUT")
|
||||
|
||||
for j in range(self.rows):
|
||||
for j in range(self.num_outputs):
|
||||
self.add_pin("decode_{0}".format(j), "OUTPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
|
||||
def create_pre_decoder(self):
|
||||
""" Creates pre-decoder and places labels input address [A] """
|
||||
|
||||
|
|
@ -248,7 +267,7 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(self.no_of_pre3x8):
|
||||
self.create_pre3x8(i)
|
||||
|
||||
def create_pre2x4(self,num):
|
||||
def create_pre2x4(self, num):
|
||||
""" Add a 2x4 predecoder to the left of the origin """
|
||||
|
||||
if (self.num_inputs == 2):
|
||||
|
|
@ -268,8 +287,7 @@ class hierarchical_decoder(design.design):
|
|||
mod=self.pre2_4))
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
||||
def create_pre3x8(self,num):
|
||||
def create_pre3x8(self, num):
|
||||
""" Add 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||
# If we had 2x4 predecodes, those are used as the lower
|
||||
# decode output bits
|
||||
|
|
@ -283,11 +301,10 @@ class hierarchical_decoder(design.design):
|
|||
pins.append("out_{0}".format(output_index + out_index_offset))
|
||||
pins.extend(["vdd", "gnd"])
|
||||
|
||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
||||
mod=self.pre3_8))
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
||||
def place_pre_decoder(self):
|
||||
""" Creates pre-decoder and places labels input address [A] """
|
||||
|
||||
|
|
@ -297,212 +314,151 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(self.no_of_pre3x8):
|
||||
self.place_pre3x8(i)
|
||||
|
||||
def place_pre2x4(self,num):
|
||||
def place_pre2x4(self, num):
|
||||
""" Place 2x4 predecoder to the left of the origin """
|
||||
|
||||
if (self.num_inputs == 2):
|
||||
base = vector(-self.pre2_4.width,0)
|
||||
base = vector(-self.pre2_4.width, 0)
|
||||
else:
|
||||
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
||||
base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing))
|
||||
|
||||
self.pre2x4_inst[num].place(base)
|
||||
|
||||
|
||||
def place_pre3x8(self,num):
|
||||
def place_pre3x8(self, num):
|
||||
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||
if (self.num_inputs == 3):
|
||||
offset = vector(-self.pre_3_8.width,0)
|
||||
mirror ="R0"
|
||||
offset = vector(-self.pre_3_8.width, 0)
|
||||
else:
|
||||
height = self.no_of_pre2x4*self.pre2_4.height + num*self.pre3_8.height
|
||||
height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing)
|
||||
offset = vector(-self.pre3_8.width, height)
|
||||
|
||||
self.pre3x8_inst[num].place(offset)
|
||||
|
||||
|
||||
def create_row_decoder(self):
|
||||
""" Create the row-decoder by placing NAND2/NAND3 and Inverters
|
||||
""" Create the row-decoder by placing AND2/AND3 and Inverters
|
||||
and add the primary decoder output pins. """
|
||||
if (self.num_inputs >= 4):
|
||||
self.create_decoder_nand_array()
|
||||
self.create_decoder_inv_array()
|
||||
self.create_decoder_and_array()
|
||||
|
||||
def create_decoder_and_array(self):
|
||||
""" Add a column of AND gates for final decode """
|
||||
|
||||
def create_decoder_nand_array(self):
|
||||
""" Add a column of NAND gates for final decode """
|
||||
|
||||
self.nand_inst = []
|
||||
self.and_inst = []
|
||||
|
||||
# Row Decoder NAND GATE array for address inputs <5.
|
||||
# Row Decoder AND GATE array for address inputs <5.
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
for i in range(len(self.predec_groups[0])):
|
||||
for j in range(len(self.predec_groups[1])):
|
||||
row = len(self.predec_groups[0])*j + i
|
||||
name = self.NAND_FORMAT.format(row)
|
||||
self.nand_inst.append(self.add_inst(name=name,
|
||||
mod=self.nand2))
|
||||
pins =["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"Z_{0}".format(row),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
output = len(self.predec_groups[0]) * j + i
|
||||
if (output < self.num_outputs):
|
||||
name = self.AND_FORMAT.format(output)
|
||||
self.and_inst.append(self.add_inst(name=name,
|
||||
mod=self.and2))
|
||||
pins =["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"decode_{0}".format(output),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
||||
# Row Decoder NAND GATE array for address inputs >5.
|
||||
# Row Decoder AND GATE array for address inputs >5.
|
||||
elif (self.num_inputs > 5):
|
||||
for i in range(len(self.predec_groups[0])):
|
||||
for j in range(len(self.predec_groups[1])):
|
||||
for k in range(len(self.predec_groups[2])):
|
||||
row = (len(self.predec_groups[0])*len(self.predec_groups[1])) * k \
|
||||
+ len(self.predec_groups[0])*j + i
|
||||
output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
|
||||
+ len(self.predec_groups[0]) * j + i
|
||||
|
||||
name = self.NAND_FORMAT.format(row)
|
||||
self.nand_inst.append(self.add_inst(name=name,
|
||||
mod=self.nand3))
|
||||
if (output < self.num_outputs):
|
||||
name = self.AND_FORMAT.format(output)
|
||||
self.and_inst.append(self.add_inst(name=name,
|
||||
mod=self.and3))
|
||||
|
||||
pins = ["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||
"Z_{0}".format(row),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
||||
def create_decoder_inv_array(self):
|
||||
"""
|
||||
Add a column of INV gates for the decoder.
|
||||
"""
|
||||
|
||||
self.inv_inst = []
|
||||
for row in range(self.rows):
|
||||
name = self.INV_FORMAT.format(row)
|
||||
self.inv_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.connect_inst(args=["Z_{0}".format(row),
|
||||
"decode_{0}".format(row),
|
||||
"vdd", "gnd"])
|
||||
|
||||
|
||||
def place_decoder_inv_array(self):
|
||||
"""
|
||||
Place the column of INV gates for the decoder above the predecoders
|
||||
and to the right of the NAND decoders.
|
||||
"""
|
||||
|
||||
z_pin = self.inv.get_pin("Z")
|
||||
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
x_off = self.internal_routing_width + self.nand2.width
|
||||
else:
|
||||
x_off = self.internal_routing_width + self.nand3.width
|
||||
|
||||
for row in range(self.rows):
|
||||
if (row % 2 == 0):
|
||||
inv_row_height = self.inv.height * row
|
||||
mirror = "R0"
|
||||
y_dir = 1
|
||||
else:
|
||||
inv_row_height = self.inv.height * (row + 1)
|
||||
mirror = "MX"
|
||||
y_dir = -1
|
||||
y_off = inv_row_height
|
||||
offset = vector(x_off,y_off)
|
||||
self.inv_inst[row].place(offset=offset,
|
||||
mirror=mirror)
|
||||
pins = ["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||
"decode_{0}".format(output),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
def place_row_decoder(self):
|
||||
"""
|
||||
Place the row-decoder by placing NAND2/NAND3 and Inverters
|
||||
and add the primary decoder output pins.
|
||||
"""
|
||||
Place the row-decoder by placing AND2/AND3 and Inverters
|
||||
and add the primary decoder output pins.
|
||||
"""
|
||||
if (self.num_inputs >= 4):
|
||||
self.place_decoder_nand_array()
|
||||
self.place_decoder_inv_array()
|
||||
self.route_decoder()
|
||||
self.place_decoder_and_array()
|
||||
|
||||
|
||||
def place_decoder_nand_array(self):
|
||||
""" Add a column of NAND gates for final decode """
|
||||
def place_decoder_and_array(self):
|
||||
"""
|
||||
Add a column of AND gates for final decode.
|
||||
This may have more than one decoder per row to match the bitcell height.
|
||||
"""
|
||||
|
||||
# Row Decoder NAND GATE array for address inputs <5.
|
||||
# Row Decoder AND GATE array for address inputs <5.
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
self.place_nand_array(nand_mod=self.nand2)
|
||||
self.place_and_array(and_mod=self.and2)
|
||||
|
||||
# Row Decoder NAND GATE array for address inputs >5.
|
||||
# Row Decoder AND GATE array for address inputs >5.
|
||||
# FIXME: why this correct offset?)
|
||||
elif (self.num_inputs > 5):
|
||||
self.place_nand_array(nand_mod=self.nand3)
|
||||
self.place_and_array(and_mod=self.and3)
|
||||
|
||||
def place_nand_array(self, nand_mod):
|
||||
""" Add a column of NAND gates for the decoder above the predecoders."""
|
||||
|
||||
for row in range(self.rows):
|
||||
name = self.NAND_FORMAT.format(row)
|
||||
def place_and_array(self, and_mod):
|
||||
"""
|
||||
Add a column of AND gates for the decoder above the predecoders.
|
||||
"""
|
||||
|
||||
for row in range(self.num_outputs):
|
||||
if ((row % 2) == 0):
|
||||
y_off = nand_mod.height*row
|
||||
y_dir = 1
|
||||
y_off = and_mod.height * row
|
||||
mirror = "R0"
|
||||
else:
|
||||
y_off = nand_mod.height*(row + 1)
|
||||
y_dir = -1
|
||||
y_off = and_mod.height * (row + 1)
|
||||
mirror = "MX"
|
||||
|
||||
self.nand_inst[row].place(offset=[self.internal_routing_width, y_off],
|
||||
mirror=mirror)
|
||||
x_off = self.internal_routing_width
|
||||
self.and_inst[row].place(offset=vector(x_off, y_off),
|
||||
mirror=mirror)
|
||||
|
||||
|
||||
def route_outputs(self):
|
||||
""" Add the pins. """
|
||||
|
||||
|
||||
def route_decoder(self):
|
||||
""" Route the nand to inverter in the decoder and add the pins. """
|
||||
|
||||
for row in range(self.rows):
|
||||
|
||||
# route nand output to output inv input
|
||||
zr_pos = self.nand_inst[row].get_pin("Z").rc()
|
||||
al_pos = self.inv_inst[row].get_pin("A").lc()
|
||||
# ensure the bend is in the middle
|
||||
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
|
||||
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
|
||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||
|
||||
z_pin = self.inv_inst[row].get_pin("Z")
|
||||
self.add_layout_pin(text="decode_{0}".format(row),
|
||||
layer="metal1",
|
||||
offset=z_pin.ll(),
|
||||
width=z_pin.width(),
|
||||
height=z_pin.height())
|
||||
for row in range(self.num_outputs):
|
||||
and_inst = self.and_inst[row]
|
||||
self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row))
|
||||
|
||||
|
||||
|
||||
def route_predecode_rails(self):
|
||||
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
|
||||
def route_decoder_bus(self):
|
||||
"""
|
||||
Creates vertical metal 2 bus to connect predecoder and decoder stages.
|
||||
"""
|
||||
|
||||
# This is not needed for inputs <4 since they have no pre/decode stages.
|
||||
if (self.num_inputs >= 4):
|
||||
input_offset = vector(0.5*self.m2_width,0)
|
||||
# This leaves an offset for the predecoder output jogs
|
||||
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
||||
self.predecode_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=self.height)
|
||||
|
||||
self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer,
|
||||
pitch=self.bus_pitch,
|
||||
offset=vector(self.bus_pitch, 0),
|
||||
names=input_bus_names,
|
||||
length=self.height)
|
||||
|
||||
self.route_rails_to_predecodes()
|
||||
self.route_rails_to_decoder()
|
||||
|
||||
def route_rails_to_predecodes(self):
|
||||
""" Iterates through all of the predecodes and connects to the rails including the offsets """
|
||||
self.route_predecodes_to_bus()
|
||||
self.route_bus_to_decoder()
|
||||
|
||||
def route_predecodes_to_bus(self):
|
||||
"""
|
||||
Iterates through all of the predecodes
|
||||
and connects to the rails including the offsets
|
||||
"""
|
||||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre2x4):
|
||||
for i in range(4):
|
||||
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
|
||||
x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch
|
||||
y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height
|
||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||
|
||||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre3x8):
|
||||
|
|
@ -510,91 +466,147 @@ class hierarchical_decoder(design.design):
|
|||
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch
|
||||
y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height
|
||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset)
|
||||
|
||||
|
||||
|
||||
def route_rails_to_decoder(self):
|
||||
""" Use the self.predec_groups to determine the connections to the decoder NAND gates.
|
||||
Inputs of NAND2/NAND3 gates come from different groups.
|
||||
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
|
||||
[8,9,10,11,12,13,14,15] ] the first NAND3 inputs are connected to
|
||||
[0,4,8] and second NAND3 is connected to [0,4,9] ........... and the
|
||||
128th NAND3 is connected to [3,7,15]
|
||||
def route_bus_to_decoder(self):
|
||||
"""
|
||||
row_index = 0
|
||||
Use the self.predec_groups to determine the connections to the decoder AND gates.
|
||||
Inputs of AND2/AND3 gates come from different groups.
|
||||
For example for these groups
|
||||
[ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||
the first AND3 inputs are connected to [0,4,8],
|
||||
second AND3 is connected to [0,4,9],
|
||||
...
|
||||
and the 128th AND3 is connected to [3,7,15]
|
||||
"""
|
||||
output_index = 0
|
||||
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
for index_B in self.predec_groups[1]:
|
||||
for index_A in self.predec_groups[0]:
|
||||
# FIXME: convert to connect_bus?
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||
row_index = row_index + 1
|
||||
if (output_index < self.num_outputs):
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("A"),
|
||||
output_index)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("B"),
|
||||
output_index)
|
||||
output_index = output_index + 1
|
||||
|
||||
elif (self.num_inputs > 5):
|
||||
for index_C in self.predec_groups[2]:
|
||||
for index_B in self.predec_groups[1]:
|
||||
for index_A in self.predec_groups[0]:
|
||||
# FIXME: convert to connect_bus?
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||
predecode_name = "predecode_{}".format(index_C)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
|
||||
row_index = row_index + 1
|
||||
if (output_index < self.num_outputs):
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("A"),
|
||||
output_index)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("B"),
|
||||
output_index)
|
||||
predecode_name = "predecode_{}".format(index_C)
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("C"),
|
||||
output_index)
|
||||
output_index = output_index + 1
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||
|
||||
# The vias will be placed in the center and right of the cells, respectively.
|
||||
xoffset = self.nand_inst[0].cx()
|
||||
for num in range(0,self.rows):
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# The nand and inv are the same height rows...
|
||||
supply_pin = self.nand_inst[num].get_pin(pin_name)
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin_pos)
|
||||
|
||||
# Make a redundant rail too
|
||||
for num in range(0,self.rows,2):
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
start = self.nand_inst[num].get_pin(pin_name).lc()
|
||||
end = self.inv_inst[num].get_pin(pin_name).rc()
|
||||
mid = (start+end).scale(0.5,0.5)
|
||||
self.add_rect_center(layer="metal1",
|
||||
offset=mid,
|
||||
width=end.x-start.x)
|
||||
"""
|
||||
Add a pin for each row of vdd/gnd which are
|
||||
must-connects next level up.
|
||||
"""
|
||||
|
||||
|
||||
# Copy the pins from the predecoders
|
||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||
self.copy_layout_pin(pre, "vdd")
|
||||
self.copy_layout_pin(pre, "gnd")
|
||||
if OPTS.tech_name == "sky130":
|
||||
for n in ["vdd", "gnd"]:
|
||||
pins = self.and_inst[0].get_pins(n)
|
||||
for pin in pins:
|
||||
self.add_rect(layer=pin.layer,
|
||||
offset=pin.ll() + vector(0, self.bus_space),
|
||||
width=pin.width(),
|
||||
height=self.height - 2 * self.bus_space)
|
||||
|
||||
# This adds power vias at the top of each cell
|
||||
# (except the last to keep them inside the boundary)
|
||||
for i in self.and_inst[:-1]:
|
||||
pins = i.get_pins(n)
|
||||
for pin in pins:
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
for i in self.pre2x4_inst + self.pre3x8_inst:
|
||||
self.copy_layout_pin(i, n)
|
||||
else:
|
||||
# The vias will be placed at the right of the cells.
|
||||
xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space
|
||||
for row in range(0, self.num_outputs):
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# The nand and inv are the same height rows...
|
||||
supply_pin = self.and_inst[row].get_pin(pin_name)
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin_pos,
|
||||
start_layer=supply_pin.layer)
|
||||
|
||||
# Copy the pins from the predecoders
|
||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
self.copy_layout_pin(pre, pin_name)
|
||||
|
||||
def route_predecode_bus_outputs(self, rail_name, pin, row):
|
||||
"""
|
||||
Connect the routing rail to the given metal1 pin
|
||||
using a routing track at the given y_offset
|
||||
"""
|
||||
|
||||
def route_predecode_rail(self, rail_name, pin):
|
||||
""" Connect the routing rail to the given metal1 pin """
|
||||
rail_pos = vector(self.predecode_rails[rail_name].x,pin.lc().y)
|
||||
self.add_path("metal1", [rail_pos, pin.lc()])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=rail_pos)
|
||||
|
||||
|
||||
def route_predecode_rail_m3(self, rail_name, pin):
|
||||
""" Connect the routing rail to the given metal1 pin """
|
||||
pin_pos = pin.center()
|
||||
rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
|
||||
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||
|
||||
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=pin_pos,
|
||||
directions=("H", "H"))
|
||||
|
||||
def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset):
|
||||
"""
|
||||
Connect the routing rail to the given metal1 pin using a jog
|
||||
to the right of the cell at the given x_offset.
|
||||
"""
|
||||
# This routes the pin up to the rail, basically, to avoid conflicts.
|
||||
# It would be fixed with a channel router.
|
||||
mid_point = vector(pin.cx(), pin.cy()+self.inv.height/2)
|
||||
rail_pos = vector(self.predecode_rails[rail_name].x,mid_point.y)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=pin.center())
|
||||
self.add_wire(("metal3","via2","metal2"), [rail_pos, mid_point, pin.uc()])
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=rail_pos)
|
||||
pin_pos = pin.rc()
|
||||
mid_point1 = vector(x_offset, pin_pos.y)
|
||||
mid_point2 = vector(x_offset, y_offset)
|
||||
rail_pos = vector(self.predecode_bus[rail_name].cx(), mid_point2.y)
|
||||
self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos])
|
||||
|
||||
# pin_pos = pin.center()
|
||||
# rail_pos = vector(self.predecode_bus[rail_name].cx(), pin_pos.y)
|
||||
# self.add_path(self.output_layer, [pin_pos, rail_pos])
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.output_layer,
|
||||
offset=pin_pos)
|
||||
self.add_via_stack_center(from_layer=self.bus_layer,
|
||||
to_layer=self.output_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
|
||||
def input_load(self):
|
||||
if self.determine_predecodes(self.num_inputs)[1]==0:
|
||||
|
|
|
|||
|
|
@ -8,19 +8,28 @@
|
|||
import debug
|
||||
import design
|
||||
import math
|
||||
from tech import drc
|
||||
import contact
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class hierarchical_predecode(design.design):
|
||||
"""
|
||||
Pre 2x4 and 3x8 decoder shared code.
|
||||
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
|
||||
"""
|
||||
def __init__(self, name, input_number, height=None):
|
||||
self.number_of_inputs = input_number
|
||||
self.cell_height = height
|
||||
|
||||
b = factory.create(module_type="bitcell")
|
||||
if not height:
|
||||
self.cell_height = b.height
|
||||
self.column_decoder = False
|
||||
else:
|
||||
self.cell_height = height
|
||||
# If we are pitch matched to the bitcell, it's a predecoder
|
||||
# otherwise it's a column decoder (out of pgates)
|
||||
self.column_decoder = (height != b.height)
|
||||
|
||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||
design.design.__init__(self, name)
|
||||
|
||||
|
|
@ -33,70 +42,103 @@ class hierarchical_predecode(design.design):
|
|||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Add the INV and NAND gate modules """
|
||||
|
||||
self.inv = factory.create(module_type="pinv",
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
self.add_nand(self.number_of_inputs)
|
||||
self.add_mod(self.nand)
|
||||
""" Add the INV and AND gate modules """
|
||||
|
||||
def add_nand(self,inputs):
|
||||
""" Create the NAND for the predecode input stage """
|
||||
if inputs==2:
|
||||
self.nand = factory.create(module_type="pnand2",
|
||||
height=self.cell_height)
|
||||
elif inputs==3:
|
||||
self.nand = factory.create(module_type="pnand3",
|
||||
height=self.cell_height)
|
||||
else:
|
||||
debug.error("Invalid number of predecode inputs: {}".format(inputs),-1)
|
||||
debug.check(self.number_of_inputs < 4,
|
||||
"Invalid number of predecode inputs: {}".format(self.number_of_inputs))
|
||||
|
||||
if self.column_decoder:
|
||||
and_type = "pand{}".format(self.number_of_inputs)
|
||||
inv_type = "pinv"
|
||||
else:
|
||||
and_type = "and{}_dec".format(self.number_of_inputs)
|
||||
inv_type = "inv_dec"
|
||||
self.and_mod = factory.create(module_type=and_type,
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.and_mod)
|
||||
|
||||
# This uses the pinv_dec parameterized cell
|
||||
self.inv = factory.create(module_type=inv_type,
|
||||
height=self.cell_height,
|
||||
size=1)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
def create_layout(self):
|
||||
""" The general organization is from left to right:
|
||||
1) a set of M2 rails for input signals
|
||||
2) a set of inverters to invert input signals
|
||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||
4) a set of AND gates for inversion
|
||||
"""
|
||||
self.setup_layout_constraints()
|
||||
self.route_rails()
|
||||
self.place_input_inverters()
|
||||
self.place_and_array()
|
||||
self.route()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def setup_layout_constraints(self):
|
||||
|
||||
self.height = self.number_of_outputs * self.nand.height
|
||||
# Inputs to cells are on input layer
|
||||
# Outputs from cells are on output layer
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.bus_layer = "m1"
|
||||
self.bus_directions = "nonpref"
|
||||
self.bus_pitch = self.m1_pitch
|
||||
self.bus_space = 1.5 * self.m1_space
|
||||
self.input_layer = "m2"
|
||||
self.output_layer = "li"
|
||||
self.output_layer_pitch = self.li_pitch
|
||||
else:
|
||||
self.bus_layer = "m2"
|
||||
self.bus_directions = "pref"
|
||||
self.bus_pitch = self.m2_pitch
|
||||
self.bus_space = self.m2_space
|
||||
# This requires a special jog to ensure to conflicts with the output layers
|
||||
self.input_layer = "m1"
|
||||
self.output_layer = "m1"
|
||||
self.output_layer_pitch = self.m1_pitch
|
||||
|
||||
self.height = self.number_of_outputs * self.and_mod.height
|
||||
|
||||
# x offset for input inverters
|
||||
self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch
|
||||
# +1 input for spacing for supply rail contacts
|
||||
self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch
|
||||
|
||||
# x offset to NAND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches
|
||||
self.x_off_nand = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch
|
||||
# x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches
|
||||
self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch
|
||||
|
||||
# x offset to output inverters
|
||||
self.x_off_inv_2 = self.x_off_nand + self.nand.width
|
||||
|
||||
# Height width are computed
|
||||
self.width = self.x_off_inv_2 + self.inv.width
|
||||
self.width = self.x_off_and + self.and_mod.width
|
||||
|
||||
def route_rails(self):
|
||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
||||
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
offset = vector(0.5*self.m2_width,2*self.m1_width)
|
||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=offset,
|
||||
names=input_names,
|
||||
length=self.height - 2*self.m1_width)
|
||||
# Offsets for the perimeter spacing to other modules
|
||||
# This uses m3 pitch to leave space for power routes
|
||||
offset = vector(self.bus_pitch, self.bus_pitch)
|
||||
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||
offset=offset,
|
||||
names=input_names,
|
||||
length=self.height - 2 * self.bus_pitch)
|
||||
|
||||
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
decode_names = invert_names + non_invert_names
|
||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width)
|
||||
self.decode_rails = self.create_vertical_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch)
|
||||
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
|
||||
offset=offset,
|
||||
names=decode_names,
|
||||
length=self.height - 2*self.m1_width)
|
||||
length=self.height - 2 * self.bus_pitch)
|
||||
|
||||
|
||||
def create_input_inverters(self):
|
||||
""" Create the input inverters to invert input signals for the decode stage. """
|
||||
self.in_inst = []
|
||||
self.inv_inst = []
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
name = "pre_inv_{0}".format(inv_num)
|
||||
self.in_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.inv_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.connect_inst(["in_{0}".format(inv_num),
|
||||
"inbar_{0}".format(inv_num),
|
||||
"vdd", "gnd"])
|
||||
|
|
@ -104,6 +146,7 @@ class hierarchical_predecode(design.design):
|
|||
def place_input_inverters(self):
|
||||
""" Place the input inverters to invert input signals for the decode stage. """
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
|
||||
if (inv_num % 2 == 0):
|
||||
y_off = inv_num * (self.inv.height)
|
||||
mirror = "R0"
|
||||
|
|
@ -111,178 +154,204 @@ class hierarchical_predecode(design.design):
|
|||
y_off = (inv_num + 1) * (self.inv.height)
|
||||
mirror="MX"
|
||||
offset = vector(self.x_off_inv_1, y_off)
|
||||
self.in_inst[inv_num].place(offset=offset,
|
||||
mirror=mirror)
|
||||
|
||||
def create_output_inverters(self):
|
||||
""" Create inverters for the inverted output decode signals. """
|
||||
self.inv_inst = []
|
||||
for inv_num in range(self.number_of_outputs):
|
||||
name = "pre_nand_inv_{}".format(inv_num)
|
||||
self.inv_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.connect_inst(["Z_{}".format(inv_num),
|
||||
"out_{}".format(inv_num),
|
||||
"vdd", "gnd"])
|
||||
|
||||
|
||||
def place_output_inverters(self):
|
||||
""" Place inverters for the inverted output decode signals. """
|
||||
for inv_num in range(self.number_of_outputs):
|
||||
if (inv_num % 2 == 0):
|
||||
y_off = inv_num * self.inv.height
|
||||
mirror = "R0"
|
||||
else:
|
||||
y_off =(inv_num + 1)*self.inv.height
|
||||
mirror = "MX"
|
||||
offset = vector(self.x_off_inv_2, y_off)
|
||||
self.inv_inst[inv_num].place(offset=offset,
|
||||
mirror=mirror)
|
||||
|
||||
def create_and_array(self, connections):
|
||||
""" Create the AND stage for the decodes """
|
||||
self.and_inst = []
|
||||
for and_input in range(self.number_of_outputs):
|
||||
inout = str(self.number_of_inputs) + "x" + str(self.number_of_outputs)
|
||||
name = "Xpre{0}_and_{1}".format(inout, and_input)
|
||||
self.and_inst.append(self.add_inst(name=name,
|
||||
mod=self.and_mod))
|
||||
self.connect_inst(connections[and_input])
|
||||
|
||||
def create_nand_array(self,connections):
|
||||
""" Create the NAND stage for the decodes """
|
||||
self.nand_inst = []
|
||||
for nand_input in range(self.number_of_outputs):
|
||||
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
||||
name = "Xpre{0}_nand_{1}".format(inout,nand_input)
|
||||
self.nand_inst.append(self.add_inst(name=name,
|
||||
mod=self.nand))
|
||||
self.connect_inst(connections[nand_input])
|
||||
|
||||
|
||||
def place_nand_array(self):
|
||||
""" Place the NAND stage for the decodes """
|
||||
for nand_input in range(self.number_of_outputs):
|
||||
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
||||
if (nand_input % 2 == 0):
|
||||
y_off = nand_input * self.inv.height
|
||||
def place_and_array(self):
|
||||
""" Place the AND stage for the decodes """
|
||||
for and_input in range(self.number_of_outputs):
|
||||
# inout = str(self.number_of_inputs) + "x" + str(self.number_of_outputs)
|
||||
if (and_input % 2 == 0):
|
||||
y_off = and_input * self.and_mod.height
|
||||
mirror = "R0"
|
||||
else:
|
||||
y_off = (nand_input + 1) * self.inv.height
|
||||
y_off = (and_input + 1) * self.and_mod.height
|
||||
mirror = "MX"
|
||||
offset = vector(self.x_off_nand, y_off)
|
||||
self.nand_inst[nand_input].place(offset=offset,
|
||||
mirror=mirror)
|
||||
|
||||
offset = vector(self.x_off_and, y_off)
|
||||
self.and_inst[and_input].place(offset=offset,
|
||||
mirror=mirror)
|
||||
|
||||
def route(self):
|
||||
self.route_input_inverters()
|
||||
self.route_inputs_to_rails()
|
||||
self.route_nand_to_rails()
|
||||
self.route_output_inverters()
|
||||
self.route_and_to_rails()
|
||||
self.route_output_and()
|
||||
self.route_vdd_gnd()
|
||||
|
||||
def route_inputs_to_rails(self):
|
||||
""" Route the uninverted inputs to the second set of rails """
|
||||
for num in range(self.number_of_inputs):
|
||||
# route one signal next to each vdd/gnd rail since this is
|
||||
# typically where the p/n devices are and there are no
|
||||
# pins in the nand gates.
|
||||
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
|
||||
in_pin = "in_{}".format(num)
|
||||
a_pin = "A_{}".format(num)
|
||||
in_pos = vector(self.input_rails[in_pin].x,y_offset)
|
||||
a_pos = vector(self.decode_rails[a_pin].x,y_offset)
|
||||
self.add_path("metal1",[in_pos, a_pos])
|
||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
||||
offset=[self.input_rails[in_pin].x, y_offset])
|
||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
||||
offset=[self.decode_rails[a_pin].x, y_offset])
|
||||
|
||||
def route_output_inverters(self):
|
||||
top_and_gate = self.and_inst[-1]
|
||||
for num in range(self.number_of_inputs):
|
||||
if num == 0:
|
||||
pin = top_and_gate.get_pin("A")
|
||||
elif num == 1:
|
||||
pin = top_and_gate.get_pin("B")
|
||||
elif num == 2:
|
||||
pin = top_and_gate.get_pin("C")
|
||||
elif num == 3:
|
||||
pin = top_and_gate.get_pin("D")
|
||||
else:
|
||||
debug.error("Too many inputs for predecoder.", -1)
|
||||
y_offset = pin.cy()
|
||||
in_pin = "in_{}".format(num)
|
||||
a_pin = "A_{}".format(num)
|
||||
in_pos = vector(self.input_rails[in_pin].cx(), y_offset)
|
||||
a_pos = vector(self.decode_rails[a_pin].cx(), y_offset)
|
||||
self.add_path(self.input_layer, [in_pos, a_pos])
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=[self.input_rails[in_pin].cx(), y_offset])
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=[self.decode_rails[a_pin].cx(), y_offset])
|
||||
|
||||
def route_output_and(self):
|
||||
"""
|
||||
Route all conections of the outputs inverters
|
||||
Route all conections of the outputs and gates
|
||||
"""
|
||||
for num in range(self.number_of_outputs):
|
||||
|
||||
# route nand output to output inv input
|
||||
zr_pos = self.nand_inst[num].get_pin("Z").rc()
|
||||
al_pos = self.inv_inst[num].get_pin("A").lc()
|
||||
# ensure the bend is in the middle
|
||||
mid1_pos = vector(0.5*(zr_pos.x+al_pos.x), zr_pos.y)
|
||||
mid2_pos = vector(0.5*(zr_pos.x+al_pos.x), al_pos.y)
|
||||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||
|
||||
z_pin = self.inv_inst[num].get_pin("Z")
|
||||
z_pin = self.and_inst[num].get_pin("Z")
|
||||
self.add_layout_pin(text="out_{}".format(num),
|
||||
layer="metal1",
|
||||
layer=z_pin.layer,
|
||||
offset=z_pin.ll(),
|
||||
height=z_pin.height(),
|
||||
width=z_pin.width())
|
||||
|
||||
|
||||
def route_input_inverters(self):
|
||||
"""
|
||||
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
||||
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
||||
"""
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
out_pin = "Abar_{}".format(inv_num)
|
||||
in_pin = "in_{}".format(inv_num)
|
||||
|
||||
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
|
||||
|
||||
#add output so that it is just below the vdd or gnd rail
|
||||
# add output so that it is just below the vdd or gnd rail
|
||||
# since this is where the p/n devices are and there are no
|
||||
# pins in the nand gates.
|
||||
y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space
|
||||
inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc()
|
||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0)
|
||||
rail_pos = vector(self.decode_rails[out_pin].x,y_offset)
|
||||
self.add_path("metal1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
||||
self.add_via_center(layers = ("metal1", "via1", "metal2"),
|
||||
offset=rail_pos)
|
||||
|
||||
# pins in the and gates.
|
||||
inv_out_pos = inv_out_pin.rc()
|
||||
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
|
||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
|
||||
rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
|
||||
self.add_path(self.output_layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos])
|
||||
|
||||
self.add_via_stack_center(from_layer=inv_out_pin.layer,
|
||||
to_layer=self.output_layer,
|
||||
offset=inv_out_pos)
|
||||
self.add_via_stack_center(from_layer=self.output_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
|
||||
#route input
|
||||
inv_in_pos = self.in_inst[inv_num].get_pin("A").lc()
|
||||
in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y)
|
||||
self.add_path("metal1", [in_pos, inv_in_pos])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=in_pos)
|
||||
# route input
|
||||
pin = self.inv_inst[inv_num].get_pin("A")
|
||||
inv_in_pos = pin.center()
|
||||
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
|
||||
self.add_path(self.input_layer, [in_pos, inv_in_pos])
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=inv_in_pos)
|
||||
via=self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=in_pos)
|
||||
# Create the input pin at this location on the rail
|
||||
self.add_layout_pin_rect_center(text=in_pin,
|
||||
layer=self.bus_layer,
|
||||
offset=in_pos,
|
||||
height=via.mod.second_layer_height,
|
||||
width=via.mod.second_layer_width)
|
||||
|
||||
|
||||
def route_nand_to_rails(self):
|
||||
# This 2D array defines the connection mapping
|
||||
nand_input_line_combination = self.get_nand_input_line_combination()
|
||||
def route_and_to_rails(self):
|
||||
# This 2D array defines the connection mapping
|
||||
and_input_line_combination = self.get_and_input_line_combination()
|
||||
for k in range(self.number_of_outputs):
|
||||
# create x offset list
|
||||
index_lst= nand_input_line_combination[k]
|
||||
# create x offset list
|
||||
index_lst= and_input_line_combination[k]
|
||||
|
||||
if self.number_of_inputs == 2:
|
||||
gate_lst = ["A","B"]
|
||||
gate_lst = ["A", "B"]
|
||||
else:
|
||||
gate_lst = ["A","B","C"]
|
||||
gate_lst = ["A", "B", "C"]
|
||||
|
||||
# this will connect pins A,B or A,B,C
|
||||
for rail_pin,gate_pin in zip(index_lst,gate_lst):
|
||||
pin_pos = self.nand_inst[k].get_pin(gate_pin).lc()
|
||||
rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y)
|
||||
self.add_path("metal1", [rail_pos, pin_pos])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=rail_pos)
|
||||
|
||||
|
||||
|
||||
for rail_pin, gate_pin in zip(index_lst, gate_lst):
|
||||
pin = self.and_inst[k].get_pin(gate_pin)
|
||||
pin_pos = pin.center()
|
||||
rail_pos = vector(self.decode_rails[rail_pin].cx(), pin_pos.y)
|
||||
self.add_path(self.input_layer, [rail_pos, pin_pos])
|
||||
self.add_via_stack_center(from_layer=self.input_layer,
|
||||
to_layer=self.bus_layer,
|
||||
offset=rail_pos,
|
||||
directions=self.bus_directions)
|
||||
if gate_pin == "A":
|
||||
direction = None
|
||||
else:
|
||||
direction = ("H", "H")
|
||||
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.input_layer,
|
||||
offset=pin_pos,
|
||||
directions=direction)
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
in_xoffset = self.in_inst[0].rx()
|
||||
out_xoffset = self.inv_inst[0].lx() - self.m1_space
|
||||
for num in range(0,self.number_of_outputs):
|
||||
# this will result in duplicate polygons for rails, but who cares
|
||||
|
||||
# Route both supplies
|
||||
# In sky130, we use hand-made decoder cells with vertical power
|
||||
if OPTS.tech_name == "sky130" and not self.column_decoder:
|
||||
for n in ["vdd", "gnd"]:
|
||||
nand_pin = self.nand_inst[num].get_pin(n)
|
||||
supply_offset = nand_pin.ll().scale(0,1)
|
||||
self.add_rect(layer="metal1",
|
||||
offset=supply_offset,
|
||||
width=self.inv_inst[num].rx())
|
||||
# This makes a wire from top to bottom for both inv and and gates
|
||||
for i in [self.inv_inst, self.and_inst]:
|
||||
bot_pins = i[0].get_pins(n)
|
||||
top_pins = i[-1].get_pins(n)
|
||||
for (bot_pin, top_pin) in zip(bot_pins, top_pins):
|
||||
self.add_rect(layer=bot_pin.layer,
|
||||
offset=vector(bot_pin.lx(), self.bus_pitch),
|
||||
width=bot_pin.width(),
|
||||
height=top_pin.uy() - self.bus_pitch)
|
||||
# This adds power vias at the top of each cell
|
||||
# (except the last to keep them inside the boundary)
|
||||
for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]:
|
||||
pins = i.get_pins(n)
|
||||
for pin in pins:
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin.uc(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
# In other techs, we are using standard cell decoder cells with horizontal power
|
||||
else:
|
||||
for num in range(0, self.number_of_outputs):
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [in_xoffset, out_xoffset]:
|
||||
pin_pos = vector(xoffset, nand_pin.cy())
|
||||
self.add_power_pin(n, pin_pos)
|
||||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
and_pins = self.and_inst[num].get_pins(n)
|
||||
for and_pin in and_pins:
|
||||
self.add_segment_center(layer=and_pin.layer,
|
||||
start=vector(0, and_pin.cy()),
|
||||
end=vector(self.width, and_pin.cy()))
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [self.inv_inst[0].lx() - self.bus_space,
|
||||
self.and_inst[0].lx() - self.bus_space]:
|
||||
pin_pos = vector(xoffset, and_pin.cy())
|
||||
self.add_power_pin(name=n,
|
||||
loc=pin_pos,
|
||||
start_layer=and_pin.layer)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,10 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import design
|
||||
from vector import vector
|
||||
from hierarchical_predecode import hierarchical_predecode
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class hierarchical_predecode2x4(hierarchical_predecode):
|
||||
"""
|
||||
Pre 2x4 decoder used in hierarchical_decoder.
|
||||
|
|
@ -27,33 +24,16 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_input_inverters()
|
||||
self.create_output_inverters()
|
||||
connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "Z_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "Z_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "Z_3", "vdd", "gnd"]]
|
||||
self.create_nand_array(connections)
|
||||
connections =[["inbar_0", "inbar_1", "out_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "out_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "out_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "out_3", "vdd", "gnd"]]
|
||||
self.create_and_array(connections)
|
||||
|
||||
def create_layout(self):
|
||||
""" The general organization is from left to right:
|
||||
1) a set of M2 rails for input signals
|
||||
2) a set of inverters to invert input signals
|
||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||
4) a set of NAND gates for inversion
|
||||
"""
|
||||
self.setup_layout_constraints()
|
||||
self.route_rails()
|
||||
self.place_input_inverters()
|
||||
self.place_output_inverters()
|
||||
self.place_nand_array()
|
||||
self.route()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def get_nand_input_line_combination(self):
|
||||
""" These are the decoder connections of the NAND gates to the A,B pins """
|
||||
def get_and_input_line_combination(self):
|
||||
""" These are the decoder connections of the AND gates to the A,B pins """
|
||||
combination = [["Abar_0", "Abar_1"],
|
||||
["A_0", "Abar_1"],
|
||||
["Abar_0", "A_1"],
|
||||
["A_0", "A_1"]]
|
||||
return combination
|
||||
return combination
|
||||
|
|
|
|||
|
|
@ -5,13 +5,10 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import design
|
||||
from vector import vector
|
||||
from hierarchical_predecode import hierarchical_predecode
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class hierarchical_predecode3x8(hierarchical_predecode):
|
||||
"""
|
||||
Pre 3x8 decoder used in hierarchical_decoder.
|
||||
|
|
@ -20,49 +17,31 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||
hierarchical_predecode.__init__(self, name, 3, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_input_inverters()
|
||||
self.create_output_inverters()
|
||||
connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"],
|
||||
["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"],
|
||||
["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]]
|
||||
self.create_nand_array(connections)
|
||||
connections=[["inbar_0", "inbar_1", "inbar_2", "out_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "inbar_2", "out_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "inbar_2", "out_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "inbar_2", "out_3", "vdd", "gnd"],
|
||||
["inbar_0", "inbar_1", "in_2", "out_4", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "in_2", "out_5", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "in_2", "out_6", "vdd", "gnd"],
|
||||
["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]]
|
||||
self.create_and_array(connections)
|
||||
|
||||
def create_layout(self):
|
||||
"""
|
||||
The general organization is from left to right:
|
||||
1) a set of M2 rails for input signals
|
||||
2) a set of inverters to invert input signals
|
||||
3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs
|
||||
4) a set of NAND gates for inversion
|
||||
"""
|
||||
self.setup_layout_constraints()
|
||||
self.route_rails()
|
||||
self.place_input_inverters()
|
||||
self.place_output_inverters()
|
||||
self.place_nand_array()
|
||||
self.route()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
def get_nand_input_line_combination(self):
|
||||
def get_and_input_line_combination(self):
|
||||
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
||||
combination = [["Abar_0", "Abar_1", "Abar_2"],
|
||||
["A_0", "Abar_1", "Abar_2"],
|
||||
["Abar_0", "A_1", "Abar_2"],
|
||||
["A_0", "A_1", "Abar_2"],
|
||||
["Abar_0", "Abar_1", "A_2"],
|
||||
["Abar_0", "Abar_1", "A_2"],
|
||||
["A_0", "Abar_1", "A_2"],
|
||||
["Abar_0", "A_1", "A_2"],
|
||||
["A_0", "A_1", "A_2"]]
|
||||
return combination
|
||||
return combination
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
# A space for wells or jogging m2
|
||||
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"),
|
||||
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclose_active"),
|
||||
2*self.m2_pitch)
|
||||
|
||||
|
||||
|
|
@ -451,14 +451,14 @@ class multibank(design.design):
|
|||
# Connect the inverter output to the central bus
|
||||
out_pos = self.bank_select_inst.get_pin(gated_name).rc()
|
||||
bus_pos = vector(self.bus_xoffset[gated_name], out_pos.y)
|
||||
self.add_path("metal3",[out_pos, bus_pos])
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
self.add_path("m3",[out_pos, bus_pos])
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=bus_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=out_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=out_pos,
|
||||
rotate=90)
|
||||
|
||||
|
|
@ -512,7 +512,7 @@ class multibank(design.design):
|
|||
# 2 pitches on the right for vias/jogs to access the inputs
|
||||
control_bus_offset = vector(-self.m2_pitch * self.num_control_lines - self.m2_width, 0)
|
||||
control_bus_length = self.bitcell_array_inst.uy()
|
||||
self.bus_xoffset = self.create_vertical_bus(layer="metal2",
|
||||
self.bus_xoffset = self.create_vertical_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=control_bus_offset,
|
||||
names=self.control_signals,
|
||||
|
|
@ -530,9 +530,9 @@ class multibank(design.design):
|
|||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
||||
|
||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||
self.add_path("m2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||
self.add_path("metal2",[precharge_br, vector(precharge_br.x,yoffset),
|
||||
self.add_path("m2",[precharge_br, vector(precharge_br.x,yoffset),
|
||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||
|
||||
|
||||
|
|
@ -550,9 +550,9 @@ class multibank(design.design):
|
|||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||
|
||||
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||
self.add_path("m2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||
vector(bitcell_bl.x,yoffset), bitcell_bl])
|
||||
self.add_path("metal2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
||||
self.add_path("m2",[col_mux_br, vector(col_mux_br.x,yoffset),
|
||||
vector(bitcell_br.x,yoffset), bitcell_br])
|
||||
|
||||
def route_sense_amp_to_col_mux_or_bitcell_array(self):
|
||||
|
|
@ -573,9 +573,9 @@ class multibank(design.design):
|
|||
|
||||
|
||||
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||
self.add_path("metal2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
||||
self.add_path("m2",[sense_amp_bl, vector(sense_amp_bl.x,yoffset),
|
||||
vector(connect_bl.x,yoffset), connect_bl])
|
||||
self.add_path("metal2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
||||
self.add_path("m2",[sense_amp_br, vector(sense_amp_br.x,yoffset),
|
||||
vector(connect_br.x,yoffset), connect_br])
|
||||
|
||||
def route_sense_amp_to_trigate(self):
|
||||
|
|
@ -586,11 +586,11 @@ class multibank(design.design):
|
|||
tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc()
|
||||
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
||||
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=tri_gate_in)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=sa_data_out)
|
||||
self.add_path("metal3",[sa_data_out,tri_gate_in])
|
||||
self.add_path("m3",[sa_data_out,tri_gate_in])
|
||||
|
||||
def route_sense_amp_out(self):
|
||||
""" Add pins for the sense amp output """
|
||||
|
|
@ -644,14 +644,14 @@ class multibank(design.design):
|
|||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc()
|
||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
|
||||
# The mid guarantees we exit the input cell to the right.
|
||||
driver_wl_pos = self.wordline_driver_inst.get_pin("wl_{}".format(i)).rc()
|
||||
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl_{}".format(i)).lc()
|
||||
mid1 = driver_wl_pos.scale(0.5,1)+bitcell_wl_pos.scale(0.5,0)
|
||||
mid2 = driver_wl_pos.scale(0.5,0)+bitcell_wl_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||
self.add_path("m1", [driver_wl_pos, mid1, mid2, bitcell_wl_pos])
|
||||
|
||||
|
||||
|
||||
|
|
@ -698,8 +698,8 @@ class multibank(design.design):
|
|||
else:
|
||||
mid1_pos = vector(decode_out_pos.x + delta_offset + (self.num_col_addr_lines-i)*self.m2_pitch,decode_out_pos.y)
|
||||
mid2_pos = vector(mid1_pos.x,mux_addr_pos.y)
|
||||
#self.add_wire(("metal1","via1","metal2"),[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||
self.add_path("metal1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||
#self.add_wire(self.m1_stack,[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||
self.add_path("m1",[decode_out_pos, mid1_pos, mid2_pos, mux_addr_pos])
|
||||
|
||||
|
||||
|
||||
|
|
@ -715,7 +715,7 @@ class multibank(design.design):
|
|||
wl_name = "wl_{}".format(i)
|
||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||
self.add_label(text=wl_name,
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=wl_pin.center())
|
||||
|
||||
# Add the bitline names
|
||||
|
|
@ -725,10 +725,10 @@ class multibank(design.design):
|
|||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||
self.add_label(text=bl_name,
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=bl_pin.center())
|
||||
self.add_label(text=br_name,
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=br_pin.center())
|
||||
|
||||
# # Add the data output names to the sense amp output
|
||||
|
|
@ -736,7 +736,7 @@ class multibank(design.design):
|
|||
# data_name = "data_{}".format(i)
|
||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||
# self.add_label(text="sa_out_{}".format(i),
|
||||
# layer="metal2",
|
||||
# layer="m2",
|
||||
# offset=data_pin.center())
|
||||
|
||||
# Add labels on the decoder
|
||||
|
|
@ -745,7 +745,7 @@ class multibank(design.design):
|
|||
pin_name = "in_{}".format(i)
|
||||
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||
self.add_label(text=data_name,
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=data_pin.center())
|
||||
|
||||
|
||||
|
|
@ -765,8 +765,8 @@ class multibank(design.design):
|
|||
|
||||
for (control_signal, pin_pos) in connection:
|
||||
control_pos = vector(self.bus_xoffset[control_signal].x ,pin_pos.y)
|
||||
self.add_path("metal1", [control_pos, pin_pos])
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_path("m1", [control_pos, pin_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=control_pos,
|
||||
rotate=90)
|
||||
|
||||
|
|
@ -776,9 +776,9 @@ class multibank(design.design):
|
|||
mid_pos = pin_pos + vector(0,self.m1_pitch)
|
||||
control_x_offset = self.bus_xoffset[control_signal].x
|
||||
control_pos = vector(control_x_offset + self.m1_width, mid_pos.y)
|
||||
self.add_wire(("metal1","via1","metal2"),[pin_pos, mid_pos, control_pos])
|
||||
self.add_wire(self.m1_stack,[pin_pos, mid_pos, control_pos])
|
||||
control_via_pos = vector(control_x_offset, mid_pos.y)
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=control_via_pos,
|
||||
rotate=90)
|
||||
|
||||
|
|
@ -791,13 +791,13 @@ class multibank(design.design):
|
|||
if self.num_banks > 1:
|
||||
# it's not an input pin if we have multiple banks
|
||||
self.add_label_pin(text=ctrl,
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=vector(x_offset, self.min_y_offset),
|
||||
width=self.m2_width,
|
||||
height=self.max_y_offset-self.min_y_offset)
|
||||
else:
|
||||
self.add_layout_pin(text=ctrl,
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=vector(x_offset, self.min_y_offset),
|
||||
width=self.m2_width,
|
||||
height=self.max_y_offset-self.min_y_offset)
|
||||
|
|
@ -807,12 +807,12 @@ class multibank(design.design):
|
|||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||
in_pin = inst.get_pin(pin).lc()
|
||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
# Bring it up to M2 for M2/M3 routing
|
||||
self.add_via(layers=("metal1","via1","metal2"),
|
||||
offset=in_pin + contact.m1m2.offset,
|
||||
self.add_via(layers=self.m1_stack,
|
||||
offset=in_pin + contact.m1_via.offset,
|
||||
rotate=90)
|
||||
self.add_via(layers=("metal2","via2","metal3"),
|
||||
self.add_via(layers=self.m2_stack,
|
||||
offset=in_pin + self.m2m3_via_offset,
|
||||
rotate=90)
|
||||
|
||||
|
|
@ -821,10 +821,10 @@ class multibank(design.design):
|
|||
""" Helper routine to connect an unrotated/mirrored oriented instance to the rails """
|
||||
in_pin = inst.get_pin(pin).rc()
|
||||
rail_pos = vector(self.rail_1_x_offsets[rail], in_pin.y)
|
||||
self.add_wire(("metal3","via2","metal2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
self.add_via(layers=("metal1","via1","metal2"),
|
||||
offset=in_pin + contact.m1m2.offset,
|
||||
self.add_wire(("m3","via2","m2"),[in_pin, rail_pos, rail_pos - vector(0,self.m2_pitch)])
|
||||
self.add_via(layers=self.m1_stack,
|
||||
offset=in_pin + contact.m1_via.offset,
|
||||
rotate=90)
|
||||
self.add_via(layers=("metal2","via2","metal3"),
|
||||
self.add_via(layers=self.m2_stack,
|
||||
offset=in_pin + self.m2m3_via_offset,
|
||||
rotate=90)
|
||||
|
|
|
|||
|
|
@ -3,16 +3,15 @@
|
|||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys
|
||||
from tech import drc, parameter
|
||||
from math import log
|
||||
from math import log, ceil
|
||||
import debug
|
||||
import design
|
||||
from sram_factory import factory
|
||||
from vector import vector
|
||||
|
||||
from tech import layer
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class port_address(design.design):
|
||||
"""
|
||||
Create the address port (row decoder and wordline driver)..
|
||||
|
|
@ -22,20 +21,19 @@ class port_address(design.design):
|
|||
|
||||
self.num_cols = cols
|
||||
self.num_rows = rows
|
||||
self.addr_size = int(log(self.num_rows, 2))
|
||||
self.addr_size = ceil(log(self.num_rows, 2))
|
||||
|
||||
if name == "":
|
||||
name = "port_address_{0}_{1}".format(cols,rows)
|
||||
name = "port_address_{0}_{1}".format(cols, rows)
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "create data port of cols {0} rows {1}".format(cols,rows))
|
||||
debug.info(2, "create data port of cols {0} rows {1}".format(cols, rows))
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
|
||||
debug.check(len(self.all_ports) <= 2, "Bank layout cannot handle more than two ports.")
|
||||
self.create_layout()
|
||||
self.add_boundary()
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
|
|
@ -43,6 +41,10 @@ class port_address(design.design):
|
|||
self.create_wordline_driver()
|
||||
|
||||
def create_layout(self):
|
||||
if "li" in layer:
|
||||
self.route_layer = "li"
|
||||
else:
|
||||
self.route_layer = "m1"
|
||||
self.place_instances()
|
||||
self.route_layout()
|
||||
self.DRC_LVS()
|
||||
|
|
@ -51,16 +53,15 @@ class port_address(design.design):
|
|||
""" Adding pins for port address module"""
|
||||
|
||||
for bit in range(self.addr_size):
|
||||
self.add_pin("addr_{0}".format(bit),"INPUT")
|
||||
self.add_pin("addr_{0}".format(bit), "INPUT")
|
||||
|
||||
self.add_pin("wl_en", "INPUT")
|
||||
|
||||
for bit in range(self.num_rows):
|
||||
self.add_pin("wl_{0}".format(bit),"OUTPUT")
|
||||
self.add_pin("wl_{0}".format(bit), "OUTPUT")
|
||||
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def route_layout(self):
|
||||
""" Create routing amoung the modules """
|
||||
|
|
@ -71,8 +72,8 @@ class port_address(design.design):
|
|||
def route_supplies(self):
|
||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst,"vdd")
|
||||
self.copy_power_pins(inst,"gnd")
|
||||
self.copy_power_pins(inst, "vdd")
|
||||
self.copy_power_pins(inst, "gnd")
|
||||
|
||||
def route_pins(self):
|
||||
for row in range(self.addr_size):
|
||||
|
|
@ -88,31 +89,35 @@ class port_address(design.design):
|
|||
def route_internal(self):
|
||||
for row in range(self.num_rows):
|
||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc()
|
||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc()
|
||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row))
|
||||
decoder_out_pos = decoder_out_pin.rc()
|
||||
driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row))
|
||||
driver_in_pos = driver_in_pin.lc()
|
||||
self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos, var_offset=0.3)
|
||||
|
||||
self.add_via_stack_center(from_layer=decoder_out_pin.layer,
|
||||
to_layer=self.route_layer,
|
||||
offset=decoder_out_pos)
|
||||
|
||||
|
||||
self.add_via_stack_center(from_layer=driver_in_pin.layer,
|
||||
to_layer=self.route_layer,
|
||||
offset=driver_in_pos)
|
||||
|
||||
def add_modules(self):
|
||||
|
||||
self.row_decoder = factory.create(module_type="decoder",
|
||||
rows=self.num_rows)
|
||||
num_outputs=self.num_rows)
|
||||
self.add_mod(self.row_decoder)
|
||||
|
||||
self.wordline_driver = factory.create(module_type="wordline_driver",
|
||||
self.wordline_driver = factory.create(module_type="wordline_driver_array",
|
||||
rows=self.num_rows,
|
||||
cols=self.num_cols)
|
||||
self.add_mod(self.wordline_driver)
|
||||
|
||||
|
||||
def create_row_decoder(self):
|
||||
""" Create the hierarchical row decoder """
|
||||
|
||||
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
||||
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
||||
mod=self.row_decoder)
|
||||
|
||||
temp = []
|
||||
|
|
@ -123,12 +128,10 @@ class port_address(design.design):
|
|||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
|
||||
def create_wordline_driver(self):
|
||||
""" Create the Wordline Driver """
|
||||
|
||||
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
|
||||
self.wordline_driver_inst = self.add_inst(name="wordline_driver",
|
||||
mod=self.wordline_driver)
|
||||
|
||||
temp = []
|
||||
|
|
@ -141,20 +144,13 @@ class port_address(design.design):
|
|||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
|
||||
def place_instances(self):
|
||||
"""
|
||||
Compute the offsets and place the instances.
|
||||
"""
|
||||
|
||||
# A space for wells or jogging m2
|
||||
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
||||
3*self.m2_pitch)
|
||||
|
||||
row_decoder_offset = vector(0,0)
|
||||
wordline_driver_offset = vector(self.row_decoder.width + self.m2_gap,0)
|
||||
|
||||
row_decoder_offset = vector(0, 0)
|
||||
wordline_driver_offset = vector(self.row_decoder.width, 0)
|
||||
self.wordline_driver_inst.place(wordline_driver_offset)
|
||||
self.row_decoder_inst.place(row_decoder_offset)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys
|
||||
from tech import drc, parameter
|
||||
from tech import drc
|
||||
import debug
|
||||
import design
|
||||
from sram_factory import factory
|
||||
from collections import namedtuple
|
||||
from vector import vector
|
||||
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class port_data(design.design):
|
||||
"""
|
||||
Create the data port (column mux, sense amps, write driver, etc.) for the given port number.
|
||||
|
|
@ -19,25 +19,52 @@ class port_data(design.design):
|
|||
"""
|
||||
|
||||
def __init__(self, sram_config, port, name=""):
|
||||
|
||||
|
||||
sram_config.set_local_config(self)
|
||||
self.port = port
|
||||
if self.write_size is not None:
|
||||
self.num_wmasks = int(self.word_size/self.write_size)
|
||||
self.num_wmasks = int(self.word_size / self.write_size)
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if self.num_spare_cols is None:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
if name == "":
|
||||
name = "port_data_{0}".format(self.port)
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "create data port of size {0} with {1} words per row".format(self.word_size,self.words_per_row))
|
||||
debug.info(2,
|
||||
"create data port of size {0} with {1} words per row".format(self.word_size,
|
||||
self.words_per_row))
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.")
|
||||
debug.check(len(self.all_ports)<=2,
|
||||
"Bank layout cannot handle more than two ports.")
|
||||
self.create_layout()
|
||||
self.add_boundary()
|
||||
|
||||
def get_bl_names(self):
|
||||
# bl lines are connect from the precharger
|
||||
return self.precharge.get_bl_names()
|
||||
|
||||
def get_br_names(self):
|
||||
# br lines are connect from the precharger
|
||||
return self.precharge.get_br_names()
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
bl_name = "bl"
|
||||
if len(self.all_ports) == 1:
|
||||
return bl_name
|
||||
else:
|
||||
return bl_name + "{}".format(port)
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
br_name = "br"
|
||||
if len(self.all_ports) == 1:
|
||||
return br_name
|
||||
else:
|
||||
return br_name + "{}".format(port)
|
||||
|
||||
def create_netlist(self):
|
||||
self.precompute_constants()
|
||||
|
|
@ -71,8 +98,6 @@ class port_data(design.design):
|
|||
else:
|
||||
self.column_mux_array_inst = None
|
||||
|
||||
|
||||
|
||||
def create_layout(self):
|
||||
self.compute_instance_offsets()
|
||||
self.place_instances()
|
||||
|
|
@ -80,33 +105,42 @@ class port_data(design.design):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
""" Adding pins for port address module"""
|
||||
""" Adding pins for port data module"""
|
||||
|
||||
self.add_pin("rbl_bl","INOUT")
|
||||
self.add_pin("rbl_br","INOUT")
|
||||
self.add_pin("rbl_bl", "INOUT")
|
||||
self.add_pin("rbl_br", "INOUT")
|
||||
for bit in range(self.num_cols):
|
||||
self.add_pin("{0}_{1}".format(self.bl_names[self.port], bit),"INOUT")
|
||||
self.add_pin("{0}_{1}".format(self.br_names[self.port], bit),"INOUT")
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
self.add_pin("{0}_{1}".format(bl_name, bit), "INOUT")
|
||||
self.add_pin("{0}_{1}".format(br_name, bit), "INOUT")
|
||||
for bit in range(self.num_spare_cols):
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
self.add_pin("spare{0}_{1}".format(bl_name, bit), "INOUT")
|
||||
self.add_pin("spare{0}_{1}".format(br_name, bit), "INOUT")
|
||||
|
||||
if self.port in self.read_ports:
|
||||
for bit in range(self.word_size):
|
||||
self.add_pin("dout_{}".format(bit),"OUTPUT")
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("dout_{}".format(bit), "OUTPUT")
|
||||
if self.port in self.write_ports:
|
||||
for bit in range(self.word_size):
|
||||
self.add_pin("din_{}".format(bit),"INPUT")
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
self.add_pin("din_{}".format(bit), "INPUT")
|
||||
# Will be empty if no col addr lines
|
||||
sel_names = ["sel_{}".format(x) for x in range(self.num_col_addr_lines)]
|
||||
for pin_name in sel_names:
|
||||
self.add_pin(pin_name,"INPUT")
|
||||
self.add_pin(pin_name, "INPUT")
|
||||
if self.port in self.read_ports:
|
||||
self.add_pin("s_en", "INPUT")
|
||||
self.add_pin("p_en_bar", "INPUT")
|
||||
if self.port in self.write_ports:
|
||||
self.add_pin("w_en", "INPUT")
|
||||
for bit in range(self.num_wmasks):
|
||||
self.add_pin("bank_wmask_{}".format(bit),"INPUT")
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
|
||||
self.add_pin("bank_wmask_{}".format(bit), "INPUT")
|
||||
for bit in range(self.num_spare_cols):
|
||||
self.add_pin("bank_spare_wen{}".format(bit), "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def route_layout(self):
|
||||
""" Create routing among the modules """
|
||||
|
|
@ -148,29 +182,34 @@ class port_data(design.design):
|
|||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst,"vdd")
|
||||
self.copy_power_pins(inst,"gnd")
|
||||
self.copy_power_pins(inst, "vdd")
|
||||
self.copy_power_pins(inst, "gnd")
|
||||
|
||||
def add_modules(self):
|
||||
|
||||
# Extra column +1 is for RBL
|
||||
# Precharge will be shifted left if needed
|
||||
# Column offset is set to port so extra column can be on left or right
|
||||
# and mirroring happens correctly
|
||||
self.precharge_array = factory.create(module_type="precharge_array",
|
||||
columns=self.num_cols + 1,
|
||||
columns=self.num_cols + self.num_spare_cols + 1,
|
||||
bitcell_bl=self.bl_names[self.port],
|
||||
bitcell_br=self.br_names[self.port])
|
||||
bitcell_br=self.br_names[self.port],
|
||||
column_offset=self.port - 1)
|
||||
self.add_mod(self.precharge_array)
|
||||
|
||||
if self.port in self.read_ports:
|
||||
# RBLs don't get a sense amp
|
||||
self.sense_amp_array = factory.create(module_type="sense_amp_array",
|
||||
word_size=self.word_size,
|
||||
words_per_row=self.words_per_row)
|
||||
words_per_row=self.words_per_row,
|
||||
num_spare_cols=self.num_spare_cols)
|
||||
self.add_mod(self.sense_amp_array)
|
||||
else:
|
||||
self.sense_amp_array = None
|
||||
|
||||
|
||||
if self.col_addr_size > 0:
|
||||
# RBLs dont get a col mux
|
||||
self.column_mux_array = factory.create(module_type="column_mux_array",
|
||||
columns=self.num_cols,
|
||||
word_size=self.word_size,
|
||||
|
|
@ -180,19 +219,20 @@ class port_data(design.design):
|
|||
else:
|
||||
self.column_mux_array = None
|
||||
|
||||
|
||||
if self.port in self.write_ports:
|
||||
# RBLs dont get a write driver
|
||||
self.write_driver_array = factory.create(module_type="write_driver_array",
|
||||
columns=self.num_cols,
|
||||
word_size=self.word_size,
|
||||
write_size=self.write_size)
|
||||
write_size=self.write_size,
|
||||
num_spare_cols=self.num_spare_cols)
|
||||
self.add_mod(self.write_driver_array)
|
||||
if self.write_size is not None:
|
||||
# RBLs don't get a write mask
|
||||
self.write_mask_and_array = factory.create(module_type="write_mask_and_array",
|
||||
columns=self.num_cols,
|
||||
word_size=self.word_size,
|
||||
write_size=self.write_size,
|
||||
port = self.port)
|
||||
write_size=self.write_size)
|
||||
self.add_mod(self.write_mask_and_array)
|
||||
else:
|
||||
self.write_mask_and_array = None
|
||||
|
|
@ -210,17 +250,21 @@ class port_data(design.design):
|
|||
else:
|
||||
self.num_col_addr_lines = 0
|
||||
|
||||
|
||||
# A space for wells or jogging m2 between modules
|
||||
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
|
||||
3*self.m2_pitch)
|
||||
self.m2_gap = max(2 * drc("pwell_to_nwell") + drc("nwell_enclose_active"),
|
||||
3 * self.m2_pitch)
|
||||
|
||||
|
||||
# create arrays of bitline and bitline_bar names for read, write, or all ports
|
||||
# create arrays of bitline and bitline_bar names for read,
|
||||
# write, or all ports
|
||||
self.bitcell = factory.create(module_type="bitcell")
|
||||
self.bl_names = self.bitcell.get_all_bl_names()
|
||||
self.br_names = self.bitcell.get_all_br_names()
|
||||
self.wl_names = self.bitcell.get_all_wl_names()
|
||||
# used for bl/br names
|
||||
self.precharge = factory.create(module_type="precharge",
|
||||
bitcell_bl=self.bl_names[0],
|
||||
bitcell_br=self.br_names[0])
|
||||
|
||||
def create_precharge_array(self):
|
||||
""" Creating Precharge """
|
||||
|
|
@ -230,6 +274,8 @@ class port_data(design.design):
|
|||
|
||||
self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port),
|
||||
mod=self.precharge_array)
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
|
||||
temp = []
|
||||
# Use left BLs for RBL
|
||||
|
|
@ -237,8 +283,13 @@ class port_data(design.design):
|
|||
temp.append("rbl_bl")
|
||||
temp.append("rbl_br")
|
||||
for bit in range(self.num_cols):
|
||||
temp.append(self.bl_names[self.port]+"_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port]+"_{0}".format(bit))
|
||||
temp.append("{0}_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_{1}".format(br_name, bit))
|
||||
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||
|
||||
# Use right BLs for RBL
|
||||
if self.port==1:
|
||||
temp.append("rbl_bl")
|
||||
|
|
@ -246,32 +297,32 @@ class port_data(design.design):
|
|||
temp.extend(["p_en_bar", "vdd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def place_precharge_array(self, offset):
|
||||
""" Placing Precharge """
|
||||
|
||||
self.precharge_array_inst.place(offset=offset, mirror="MX")
|
||||
|
||||
|
||||
def create_column_mux_array(self):
|
||||
""" Creating Column Mux when words_per_row > 1 . """
|
||||
|
||||
self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port),
|
||||
mod=self.column_mux_array)
|
||||
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
temp = []
|
||||
for col in range(self.num_cols):
|
||||
temp.append(self.bl_names[self.port]+"_{0}".format(col))
|
||||
temp.append(self.br_names[self.port]+"_{0}".format(col))
|
||||
temp.append("{0}_{1}".format(bl_name, col))
|
||||
temp.append("{0}_{1}".format(br_name, col))
|
||||
for word in range(self.words_per_row):
|
||||
temp.append("sel_{}".format(word))
|
||||
for bit in range(self.word_size):
|
||||
temp.append(self.bl_names[self.port]+"_out_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port]+"_out_{0}".format(bit))
|
||||
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def place_column_mux_array(self, offset):
|
||||
""" Placing Column Mux when words_per_row > 1 . """
|
||||
if self.col_addr_size == 0:
|
||||
|
|
@ -279,63 +330,79 @@ class port_data(design.design):
|
|||
|
||||
self.column_mux_array_inst.place(offset=offset, mirror="MX")
|
||||
|
||||
|
||||
def create_sense_amp_array(self):
|
||||
""" Creating Sense amp """
|
||||
self.sense_amp_array_inst = self.add_inst(name="sense_amp_array{}".format(self.port),
|
||||
mod=self.sense_amp_array)
|
||||
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
temp = []
|
||||
for bit in range(self.word_size):
|
||||
temp.append("dout_{}".format(bit))
|
||||
if self.words_per_row == 1:
|
||||
temp.append(self.bl_names[self.port]+"_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port]+"_{0}".format(bit))
|
||||
temp.append("{0}_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_{1}".format(br_name, bit))
|
||||
else:
|
||||
temp.append(self.bl_names[self.port]+"_out_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port]+"_out_{0}".format(bit))
|
||||
|
||||
temp.extend(["s_en", "vdd", "gnd"])
|
||||
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("dout_{}".format(self.word_size + bit))
|
||||
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||
|
||||
temp.append("s_en")
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def place_sense_amp_array(self, offset):
|
||||
""" Placing Sense amp """
|
||||
self.sense_amp_array_inst.place(offset=offset, mirror="MX")
|
||||
|
||||
|
||||
def create_write_driver_array(self):
|
||||
""" Creating Write Driver """
|
||||
self.write_driver_array_inst = self.add_inst(name="write_driver_array{}".format(self.port),
|
||||
mod=self.write_driver_array)
|
||||
bl_name = self.get_bl_name(self.port)
|
||||
br_name = self.get_br_name(self.port)
|
||||
|
||||
temp = []
|
||||
for bit in range(self.word_size):
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
temp.append("din_{}".format(bit))
|
||||
|
||||
for bit in range(self.word_size):
|
||||
if (self.words_per_row == 1):
|
||||
temp.append(self.bl_names[self.port] + "_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port] + "_{0}".format(bit))
|
||||
temp.append("{0}_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_{1}".format(br_name, bit))
|
||||
else:
|
||||
temp.append(self.bl_names[self.port] + "_out_{0}".format(bit))
|
||||
temp.append(self.br_names[self.port] + "_out_{0}".format(bit))
|
||||
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||
|
||||
if self.write_size is not None:
|
||||
for i in range(self.num_wmasks):
|
||||
temp.append("wdriver_sel_{}".format(i))
|
||||
for i in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{}".format(i))
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
temp.append("w_en")
|
||||
for i in range(self.num_spare_cols):
|
||||
temp.append("bank_spare_wen{}".format(i))
|
||||
else:
|
||||
temp.append("w_en")
|
||||
temp.extend(["vdd", "gnd"])
|
||||
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def place_write_driver_array(self, offset):
|
||||
""" Placing Write Driver """
|
||||
self.write_driver_array_inst.place(offset=offset, mirror="MX")
|
||||
|
||||
|
||||
def create_write_mask_and_array(self):
|
||||
""" Creating Write Mask AND Array """
|
||||
self.write_mask_and_array_inst = self.add_inst(name="write_mask_and_array{}".format(self.port),
|
||||
|
|
@ -350,12 +417,10 @@ class port_data(design.design):
|
|||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
||||
def place_write_mask_and_array(self, offset):
|
||||
""" Placing Write Mask AND array """
|
||||
self.write_mask_and_array_inst.place(offset=offset, mirror="MX")
|
||||
|
||||
|
||||
def compute_instance_offsets(self):
|
||||
"""
|
||||
Compute the empty instance offsets for port0 and port1 (if needed)
|
||||
|
|
@ -410,28 +475,24 @@ class port_data(design.design):
|
|||
if self.column_mux_offset:
|
||||
self.place_column_mux_array(self.column_mux_offset)
|
||||
|
||||
|
||||
def route_sense_amp_out(self, port):
|
||||
""" Add pins for the sense amp output """
|
||||
|
||||
for bit in range(self.word_size):
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit))
|
||||
self.add_layout_pin_rect_center(text="dout_{0}".format(bit),
|
||||
layer=data_pin.layer,
|
||||
layer=data_pin.layer,
|
||||
offset=data_pin.center(),
|
||||
height=data_pin.height(),
|
||||
width=data_pin.width())
|
||||
|
||||
|
||||
def route_write_driver_in(self, port):
|
||||
""" Connecting write driver """
|
||||
|
||||
for row in range(self.word_size):
|
||||
for row in range(self.word_size + self.num_spare_cols):
|
||||
data_name = "data_{}".format(row)
|
||||
din_name = "din_{}".format(row)
|
||||
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||
|
||||
|
||||
def route_write_mask_and_array_in(self, port):
|
||||
""" Add pins for the write mask and array input """
|
||||
|
||||
|
|
@ -440,47 +501,38 @@ class port_data(design.design):
|
|||
bank_wmask_name = "bank_wmask_{}".format(bit)
|
||||
self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name)
|
||||
|
||||
def route_write_mask_and_array_to_write_driver(self, port):
|
||||
"""
|
||||
Routing of wdriver_sel_{} between write mask AND array and
|
||||
write driver array. Adds layout pin for write
|
||||
mask AND array output and via for write driver enable
|
||||
"""
|
||||
|
||||
def route_write_mask_and_array_to_write_driver(self,port):
|
||||
""" Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write
|
||||
mask AND array output and via for write driver enable """
|
||||
wmask_inst = self.write_mask_and_array_inst
|
||||
wdriver_inst = self.write_driver_array_inst
|
||||
|
||||
inst1 = self.write_mask_and_array_inst
|
||||
inst2 = self.write_driver_array_inst
|
||||
|
||||
loc = 0
|
||||
for bit in range(self.num_wmasks):
|
||||
# Bring write mask AND array output pin to port data level
|
||||
self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
||||
self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit))
|
||||
|
||||
wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit))
|
||||
wdriver_en_pin = inst2.get_pin("en_{0}".format(bit))
|
||||
wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit))
|
||||
wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit))
|
||||
|
||||
# The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the
|
||||
# the wdriver_sel_{} pin in the write driver AND array.
|
||||
if bit == 0:
|
||||
# When the write mask output pin is right of the bitline, the target is found
|
||||
while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()):
|
||||
loc += 1
|
||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
||||
debug.check(loc<=self.num_wmasks,"Couldn't route the write mask select.")
|
||||
else:
|
||||
# Stride by the write size rather than finding the next pin to the right
|
||||
loc += self.write_size
|
||||
length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch
|
||||
|
||||
|
||||
beg_pos = wmask_out_pin.center()
|
||||
middle_pos = vector(length,wmask_out_pin.cy())
|
||||
end_pos = vector(length, wdriver_en_pin.cy())
|
||||
wmask_pos = wmask_out_pin.center()
|
||||
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
|
||||
mid_pos = vector(wdriver_pos.x, wmask_pos.y)
|
||||
|
||||
# Add driver on mask output
|
||||
self.add_via_stack_center(from_layer=wmask_out_pin.layer,
|
||||
to_layer="m1",
|
||||
offset=wmask_pos)
|
||||
# Add via for the write driver array's enable input
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=end_pos)
|
||||
self.add_via_stack_center(from_layer=wdriver_en_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=wdriver_pos)
|
||||
|
||||
# Route between write mask AND array and write driver array
|
||||
self.add_wire(("metal1","via1","metal2"), [beg_pos, middle_pos, end_pos])
|
||||
|
||||
self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])
|
||||
|
||||
def route_column_mux_to_precharge_array(self, port):
|
||||
""" Routing of BL and BR between col mux and precharge array """
|
||||
|
|
@ -488,14 +540,13 @@ class port_data(design.design):
|
|||
# Only do this if we have a column mux!
|
||||
if self.col_addr_size==0:
|
||||
return
|
||||
|
||||
inst1 = self.column_mux_array_inst
|
||||
inst2 = self.precharge_array_inst
|
||||
if self.port==0:
|
||||
self.connect_bitlines(inst1, inst2, self.num_cols, inst2_start_bit=1)
|
||||
else:
|
||||
self.connect_bitlines(inst1, inst2, self.num_cols)
|
||||
|
||||
start_bit = 1 if self.port == 0 else 0
|
||||
|
||||
self.connect_bitlines(inst1=self.column_mux_array_inst,
|
||||
inst2=self.precharge_array_inst,
|
||||
num_bits=self.num_cols,
|
||||
inst2_start_bit=start_bit)
|
||||
|
||||
def route_sense_amp_to_column_mux_or_precharge_array(self, port):
|
||||
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
||||
|
|
@ -504,47 +555,109 @@ class port_data(design.design):
|
|||
if self.col_addr_size>0:
|
||||
# Sense amp is connected to the col mux
|
||||
inst1 = self.column_mux_array_inst
|
||||
inst1_bl_name = "bl_out_{}"
|
||||
inst1_br_name = "br_out_{}"
|
||||
inst1_bls_templ = "{inst}_out_{bit}"
|
||||
start_bit = 0
|
||||
else:
|
||||
# Sense amp is directly connected to the precharge array
|
||||
inst1 = self.precharge_array_inst
|
||||
inst1_bl_name = "bl_{}"
|
||||
inst1_br_name = "br_{}"
|
||||
inst1_bls_templ="{inst}_{bit}"
|
||||
|
||||
if self.port==0:
|
||||
start_bit=1
|
||||
else:
|
||||
start_bit=0
|
||||
|
||||
|
||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
||||
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit)
|
||||
|
||||
|
||||
# spare cols connected to precharge array since they are read independently
|
||||
if self.num_spare_cols and self.col_addr_size>0:
|
||||
if self.port==0:
|
||||
off = 1
|
||||
else:
|
||||
off = 0
|
||||
|
||||
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
|
||||
inst1_bls_template="{inst}_out_{bit}",
|
||||
inst2=inst2,
|
||||
num_bits=self.word_size,
|
||||
inst1_start_bit=start_bit)
|
||||
|
||||
self.channel_route_bitlines(inst1=self.precharge_array_inst,
|
||||
inst1_bls_template="{inst}_{bit}",
|
||||
inst2=inst2,
|
||||
num_bits=self.num_spare_cols,
|
||||
inst1_start_bit=self.num_cols + off,
|
||||
inst2_start_bit=self.word_size)
|
||||
|
||||
# This could be a channel route, but in some techs the bitlines
|
||||
# are too close together.
|
||||
elif OPTS.tech_name == "sky130":
|
||||
self.connect_bitlines(inst1=inst1,
|
||||
inst1_bls_template=inst1_bls_templ,
|
||||
inst2=inst2,
|
||||
num_bits=self.word_size,
|
||||
inst1_start_bit=start_bit)
|
||||
else:
|
||||
self.channel_route_bitlines(inst1=inst1,
|
||||
inst1_bls_template=inst1_bls_templ,
|
||||
inst2=inst2,
|
||||
num_bits=self.word_size + self.num_spare_cols,
|
||||
inst1_start_bit=start_bit)
|
||||
|
||||
def route_write_driver_to_column_mux_or_precharge_array(self, port):
|
||||
""" Routing of BL and BR between sense_amp and column mux or precharge array """
|
||||
inst2 = self.write_driver_array_inst
|
||||
|
||||
|
||||
if self.col_addr_size>0:
|
||||
# Write driver is connected to the col mux
|
||||
inst1 = self.column_mux_array_inst
|
||||
inst1_bl_name = "bl_out_{}"
|
||||
inst1_br_name = "br_out_{}"
|
||||
inst1_bls_templ = "{inst}_out_{bit}"
|
||||
start_bit = 0
|
||||
else:
|
||||
# Sense amp is directly connected to the precharge array
|
||||
inst1 = self.precharge_array_inst
|
||||
inst1_bl_name = "bl_{}"
|
||||
inst1_br_name = "br_{}"
|
||||
inst1_bls_templ="{inst}_{bit}"
|
||||
if self.port==0:
|
||||
start_bit=1
|
||||
else:
|
||||
start_bit=0
|
||||
|
||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size,
|
||||
inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit)
|
||||
|
||||
if self.port==0:
|
||||
off = 1
|
||||
else:
|
||||
off = 0
|
||||
|
||||
# Channel route spare columns' bitlines
|
||||
if self.num_spare_cols and self.col_addr_size>0:
|
||||
if self.port==0:
|
||||
off = 1
|
||||
else:
|
||||
off = 0
|
||||
|
||||
self.channel_route_bitlines(inst1=self.column_mux_array_inst,
|
||||
inst1_bls_template="{inst}_out_{bit}",
|
||||
inst2=inst2,
|
||||
num_bits=self.word_size,
|
||||
inst1_start_bit=start_bit)
|
||||
|
||||
self.channel_route_bitlines(inst1=self.precharge_array_inst,
|
||||
inst1_bls_template="{inst}_{bit}",
|
||||
inst2=inst2,
|
||||
num_bits=self.num_spare_cols,
|
||||
inst1_start_bit=self.num_cols + off,
|
||||
inst2_start_bit=self.word_size)
|
||||
|
||||
# This could be a channel route, but in some techs the bitlines
|
||||
# are too close together.
|
||||
elif OPTS.tech_name == "sky130":
|
||||
self.connect_bitlines(inst1=inst1, inst2=inst2,
|
||||
num_bits=self.word_size,
|
||||
inst1_bls_template=inst1_bls_templ,
|
||||
inst1_start_bit=start_bit)
|
||||
else:
|
||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2,
|
||||
num_bits=self.word_size+self.num_spare_cols,
|
||||
inst1_bls_template=inst1_bls_templ,
|
||||
inst1_start_bit=start_bit)
|
||||
|
||||
|
||||
def route_write_driver_to_sense_amp(self, port):
|
||||
""" Routing of BL and BR between write driver and sense amp """
|
||||
|
|
@ -552,10 +665,11 @@ class port_data(design.design):
|
|||
inst1 = self.write_driver_array_inst
|
||||
inst2 = self.sense_amp_array_inst
|
||||
|
||||
# These should be pitch matched in the cell library,
|
||||
# but just in case, do a channel route.
|
||||
self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size)
|
||||
|
||||
# This could be a channel route, but in some techs the bitlines
|
||||
# are too close together.
|
||||
self.connect_bitlines(inst1=inst1,
|
||||
inst2=inst2,
|
||||
num_bits=self.word_size + self.num_spare_cols)
|
||||
|
||||
def route_bitline_pins(self):
|
||||
""" Add the bitline pins for the given port """
|
||||
|
|
@ -566,16 +680,32 @@ class port_data(design.design):
|
|||
self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br")
|
||||
bit_offset=1
|
||||
elif self.port==1:
|
||||
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl")
|
||||
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br")
|
||||
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols + self.num_spare_cols), "rbl_bl")
|
||||
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols + self.num_spare_cols), "rbl_br")
|
||||
bit_offset=0
|
||||
else:
|
||||
bit_offset=0
|
||||
|
||||
for bit in range(self.num_cols):
|
||||
if self.precharge_array_inst:
|
||||
self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(bit+bit_offset), "bl_{}".format(bit))
|
||||
self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(bit+bit_offset), "br_{}".format(bit))
|
||||
self.copy_layout_pin(self.precharge_array_inst,
|
||||
"bl_{}".format(bit + bit_offset),
|
||||
"bl_{}".format(bit))
|
||||
self.copy_layout_pin(self.precharge_array_inst,
|
||||
"br_{}".format(bit + bit_offset),
|
||||
"br_{}".format(bit))
|
||||
else:
|
||||
debug.error("Didn't find precharge array.")
|
||||
|
||||
# Copy bitlines of spare columns
|
||||
for bit in range(self.num_spare_cols):
|
||||
if self.precharge_array_inst:
|
||||
self.copy_layout_pin(self.precharge_array_inst,
|
||||
"bl_{}".format(self.num_cols + bit + bit_offset),
|
||||
"sparebl_{}".format(bit))
|
||||
self.copy_layout_pin(self.precharge_array_inst,
|
||||
"br_{}".format(self.num_cols + bit + bit_offset),
|
||||
"sparebr_{}".format(bit))
|
||||
else:
|
||||
debug.error("Didn't find precharge array.")
|
||||
|
||||
|
|
@ -589,79 +719,119 @@ class port_data(design.design):
|
|||
for pin_name in sel_names:
|
||||
self.copy_layout_pin(self.column_mux_array_inst, pin_name)
|
||||
if self.sense_amp_array_inst:
|
||||
self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en")
|
||||
self.copy_layout_pin(self.sense_amp_array_inst, "en", "s_en")
|
||||
if self.write_driver_array_inst:
|
||||
if self.write_mask_and_array_inst:
|
||||
for bit in range(self.num_wmasks):
|
||||
# Add write driver's en_{} pins
|
||||
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit), "wdriver_sel_{}".format(bit))
|
||||
for bit in range(self.num_spare_cols):
|
||||
# Add spare columns' en_{} pins
|
||||
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + self.num_wmasks), "bank_spare_wen{}".format(bit))
|
||||
elif self.num_spare_cols and not self.write_mask_and_array_inst:
|
||||
self.copy_layout_pin(self.write_driver_array_inst, "en_0", "w_en")
|
||||
for bit in range(self.num_spare_cols):
|
||||
self.copy_layout_pin(self.write_driver_array_inst, "en_{}".format(bit + 1), "bank_spare_wen{}".format(bit))
|
||||
else:
|
||||
self.copy_layout_pin(self.write_driver_array_inst, "en", "w_en")
|
||||
if self.write_mask_and_array_inst:
|
||||
self.copy_layout_pin(self.write_mask_and_array_inst, "en", "w_en")
|
||||
|
||||
|
||||
|
||||
def channel_route_bitlines(self, inst1, inst2, num_bits,
|
||||
inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0,
|
||||
inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0):
|
||||
def _group_bitline_instances(self, inst1, inst2, num_bits,
|
||||
inst1_bls_template,
|
||||
inst1_start_bit,
|
||||
inst2_bls_template,
|
||||
inst2_start_bit):
|
||||
"""
|
||||
Route the bl and br of two modules using the channel router.
|
||||
Groups all the parameters into a named tuple and seperates them into
|
||||
top and bottom instances.
|
||||
"""
|
||||
|
||||
inst_group = namedtuple('InstanceGroup', ('inst', 'bls_template',
|
||||
'bl_name', 'br_name', 'start_bit'))
|
||||
|
||||
inst1_group = inst_group(inst1, inst1_bls_template,
|
||||
inst1.mod.get_bl_name(),
|
||||
inst1.mod.get_br_name(),
|
||||
inst1_start_bit)
|
||||
inst2_group = inst_group(inst2, inst2_bls_template,
|
||||
inst2.mod.get_bl_name(),
|
||||
inst2.mod.get_br_name(),
|
||||
inst2_start_bit)
|
||||
# determine top and bottom automatically.
|
||||
# since they don't overlap, we can just check the bottom y coordinate.
|
||||
if inst1.by() < inst2.by():
|
||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
||||
bot_inst_group = inst1_group
|
||||
top_inst_group = inst2_group
|
||||
else:
|
||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
||||
bot_inst_group = inst2_group
|
||||
top_inst_group = inst1_group
|
||||
|
||||
return (bot_inst_group, top_inst_group)
|
||||
|
||||
def _get_bitline_pins(self, inst_group, bit):
|
||||
"""
|
||||
Extracts bl/br pins from an InstanceGroup based on the bit modifier.
|
||||
"""
|
||||
full_bl_name = inst_group.bls_template.format(
|
||||
**{'inst': inst_group.bl_name,
|
||||
'bit': inst_group.start_bit + bit}
|
||||
)
|
||||
full_br_name = inst_group.bls_template.format(
|
||||
**{'inst': inst_group.br_name,
|
||||
'bit': inst_group.start_bit + bit}
|
||||
)
|
||||
return (inst_group.inst.get_pin(full_bl_name),
|
||||
inst_group.inst.get_pin(full_br_name))
|
||||
|
||||
def channel_route_bitlines(self, inst1, inst2, num_bits,
|
||||
inst1_bls_template="{inst}_{bit}",
|
||||
inst1_start_bit=0,
|
||||
inst2_bls_template="{inst}_{bit}",
|
||||
inst2_start_bit=0):
|
||||
"""
|
||||
Route the bl and br of two modules using the channel router.
|
||||
"""
|
||||
|
||||
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
|
||||
inst1_bls_template, inst1_start_bit,
|
||||
inst2_bls_template, inst2_start_bit)
|
||||
|
||||
# Channel route each mux separately since we don't minimize the number
|
||||
# of tracks in teh channel router yet. If we did, we could route all the bits at once!
|
||||
offset = bottom_inst.ul() + vector(0,self.m1_pitch)
|
||||
offset = bot_inst_group.inst.ul() + vector(0, self.m1_nonpref_pitch)
|
||||
for bit in range(num_bits):
|
||||
bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit+bottom_start_bit)), bottom_inst.get_pin(bottom_br_name.format(bit+bottom_start_bit))]
|
||||
top_names = [top_inst.get_pin(top_bl_name.format(bit+top_start_bit)), top_inst.get_pin(top_br_name.format(bit+top_start_bit))]
|
||||
bottom_names = self._get_bitline_pins(bot_inst_group, bit)
|
||||
top_names = self._get_bitline_pins(top_inst_group, bit)
|
||||
|
||||
route_map = list(zip(bottom_names, top_names))
|
||||
self.create_horizontal_channel_route(route_map, offset)
|
||||
|
||||
self.create_horizontal_channel_route(route_map, offset, self.m1_stack)
|
||||
|
||||
def connect_bitlines(self, inst1, inst2, num_bits,
|
||||
inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0,
|
||||
inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0):
|
||||
|
||||
inst1_bls_template="{inst}_{bit}",
|
||||
inst1_start_bit=0,
|
||||
inst2_bls_template="{inst}_{bit}",
|
||||
inst2_start_bit=0):
|
||||
"""
|
||||
Connect the bl and br of two modules.
|
||||
This assumes that they have sufficient space to create a jog
|
||||
in the middle between the two modules (if needed).
|
||||
"""
|
||||
|
||||
# determine top and bottom automatically.
|
||||
# since they don't overlap, we can just check the bottom y coordinate.
|
||||
if inst1.by() < inst2.by():
|
||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
||||
else:
|
||||
(bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit)
|
||||
(top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit)
|
||||
|
||||
bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits,
|
||||
inst1_bls_template, inst1_start_bit,
|
||||
inst2_bls_template, inst2_start_bit)
|
||||
|
||||
for col in range(num_bits):
|
||||
bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col+bottom_start_bit)).uc()
|
||||
bottom_br = bottom_inst.get_pin(bottom_br_name.format(col+bottom_start_bit)).uc()
|
||||
top_bl = top_inst.get_pin(top_bl_name.format(col+top_start_bit)).bc()
|
||||
top_br = top_inst.get_pin(top_br_name.format(col+top_start_bit)).bc()
|
||||
bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col)
|
||||
top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col)
|
||||
bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc()
|
||||
top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc()
|
||||
|
||||
layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer))
|
||||
self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", fixed_offset=top_bl_pin.by() - layer_pitch)
|
||||
self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", fixed_offset=top_bl_pin.by() - 2 * layer_pitch)
|
||||
|
||||
yoffset = 0.5*(top_bl.y+bottom_bl.y)
|
||||
self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset),
|
||||
vector(top_bl.x,yoffset), top_bl])
|
||||
self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset),
|
||||
vector(top_br.x,yoffset), top_br])
|
||||
|
||||
def graph_exclude_precharge(self):
|
||||
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
|
||||
if self.precharge_array_inst:
|
||||
self.graph_inst_exclude.add(self.precharge_array_inst)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@
|
|||
#
|
||||
import design
|
||||
import debug
|
||||
from tech import drc
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
from tech import layer
|
||||
|
||||
|
||||
class precharge_array(design.design):
|
||||
"""
|
||||
|
|
@ -18,7 +19,7 @@ class precharge_array(design.design):
|
|||
of bit line columns, height is the height of the bit-cell array.
|
||||
"""
|
||||
|
||||
def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"):
|
||||
def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br", column_offset=0):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0}".format(self.name))
|
||||
self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br))
|
||||
|
|
@ -27,11 +28,25 @@ class precharge_array(design.design):
|
|||
self.size = size
|
||||
self.bitcell_bl = bitcell_bl
|
||||
self.bitcell_br = bitcell_br
|
||||
self.column_offset = column_offset
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.en_bar_layer = "m3"
|
||||
else:
|
||||
self.en_bar_layer = "m1"
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def get_bl_name(self):
|
||||
bl_name = self.pc_cell.get_bl_names()
|
||||
return bl_name
|
||||
|
||||
def get_br_name(self):
|
||||
br_name = self.pc_cell.get_br_names()
|
||||
return br_name
|
||||
|
||||
def add_pins(self):
|
||||
"""Adds pins for spice file"""
|
||||
for i in range(self.columns):
|
||||
|
|
@ -60,38 +75,29 @@ class precharge_array(design.design):
|
|||
size=self.size,
|
||||
bitcell_bl=self.bitcell_bl,
|
||||
bitcell_br=self.bitcell_br)
|
||||
|
||||
|
||||
self.add_mod(self.pc_cell)
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
self.add_layout_pin(text="en_bar",
|
||||
layer="metal1",
|
||||
offset=self.pc_cell.get_pin("en_bar").ll(),
|
||||
width=self.width,
|
||||
height=drc("minwidth_metal1"))
|
||||
en_pin = self.pc_cell.get_pin("en_bar")
|
||||
start_offset = en_pin.lc().scale(0, 1)
|
||||
end_offset = start_offset + vector(self.width, 0)
|
||||
self.add_layout_pin_segment_center(text="en_bar",
|
||||
layer=self.en_bar_layer,
|
||||
start=start_offset,
|
||||
end=end_offset)
|
||||
|
||||
for inst in self.local_insts:
|
||||
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||
to_layer=self.en_bar_layer,
|
||||
offset=inst.get_pin("en_bar").center())
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
|
||||
for i in range(len(self.local_insts)):
|
||||
inst = self.local_insts[i]
|
||||
bl_pin = inst.get_pin("bl")
|
||||
self.add_layout_pin(text="bl_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=drc("minwidth_metal2"),
|
||||
height=bl_pin.height())
|
||||
br_pin = inst.get_pin("br")
|
||||
self.add_layout_pin(text="br_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=br_pin.ll(),
|
||||
width=drc("minwidth_metal2"),
|
||||
height=bl_pin.height())
|
||||
self.copy_layout_pin(inst, "bl", "bl_{0}".format(i))
|
||||
self.copy_layout_pin(inst, "br", "br_{0}".format(i))
|
||||
|
||||
|
||||
def create_insts(self):
|
||||
"""Creates a precharge array by horizontally tiling the precharge cell"""
|
||||
self.local_insts = []
|
||||
|
|
@ -104,16 +110,28 @@ class precharge_array(design.design):
|
|||
self.local_insts.append(inst)
|
||||
self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en_bar", "vdd"])
|
||||
|
||||
|
||||
def place_insts(self):
|
||||
""" Places precharge array by horizontally tiling the precharge cell"""
|
||||
from tech import cell_properties
|
||||
xoffset = 0
|
||||
for i in range(self.columns):
|
||||
offset = vector(self.pc_cell.width * i, 0)
|
||||
self.local_insts[i].place(offset)
|
||||
tempx = xoffset
|
||||
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
tempx = tempx + self.pc_cell.width
|
||||
else:
|
||||
mirror = ""
|
||||
|
||||
offset = vector(tempx, 0)
|
||||
self.local_insts[i].place(offset=offset, mirror=mirror)
|
||||
xoffset = xoffset + self.pc_cell.width
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the relative capacitance of all the clk connections in the precharge array"""
|
||||
#Assume single port
|
||||
"""
|
||||
Get the relative capacitance of all the clk connections
|
||||
in the precharge array
|
||||
"""
|
||||
# Assume single port
|
||||
precharge_en_cin = self.pc_cell.get_en_cin()
|
||||
return precharge_en_cin*self.columns
|
||||
return precharge_en_cin * self.columns
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
import debug
|
||||
import design
|
||||
from tech import drc, spice
|
||||
from tech import drc, spice, cell_properties
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import logical_effort
|
||||
import bitcell_array
|
||||
import replica_column
|
||||
import dummy_array
|
||||
|
||||
|
||||
class replica_bitcell_array(design.design):
|
||||
"""
|
||||
|
|
@ -35,22 +32,23 @@ class replica_bitcell_array(design.design):
|
|||
self.right_rbl = right_rbl
|
||||
self.bitcell_ports = bitcell_ports
|
||||
|
||||
debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.")
|
||||
debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.")
|
||||
|
||||
debug.check(left_rbl + right_rbl == len(self.all_ports),
|
||||
"Invalid number of RBLs for port configuration.")
|
||||
debug.check(left_rbl + right_rbl == len(self.bitcell_ports),
|
||||
"Bitcell ports must match total RBLs.")
|
||||
|
||||
# Two dummy rows/cols plus replica for each port
|
||||
self.extra_rows = 2 + left_rbl + right_rbl
|
||||
self.extra_cols = 2 + left_rbl + right_rbl
|
||||
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
# We don't offset this because we need to align
|
||||
# the replica bitcell in the control logic
|
||||
#self.offset_all_coordinates()
|
||||
|
||||
|
||||
# self.offset_all_coordinates()
|
||||
|
||||
def create_netlist(self):
|
||||
""" Create and connect the netlist """
|
||||
self.add_modules()
|
||||
|
|
@ -58,15 +56,15 @@ class replica_bitcell_array(design.design):
|
|||
self.create_instances()
|
||||
|
||||
def add_modules(self):
|
||||
""" Array and dummy/replica columns
|
||||
""" Array and dummy/replica columns
|
||||
|
||||
d or D = dummy cell (caps to distinguish grouping)
|
||||
r or R = replica cell (caps to distinguish grouping)
|
||||
b or B = bitcell
|
||||
replica columns 1
|
||||
b or B = bitcell
|
||||
replica columns 1
|
||||
v v
|
||||
bdDDDDDDDDDDDDDDdb <- Dummy row
|
||||
bdDDDDDDDDDDDDDDrb <- Dummy row
|
||||
bdDDDDDDDDDDDDDDdb <- Dummy row
|
||||
bdDDDDDDDDDDDDDDrb <- Dummy row
|
||||
br--------------rb
|
||||
br| Array |rb
|
||||
br| row x col |rb
|
||||
|
|
@ -86,71 +84,109 @@ class replica_bitcell_array(design.design):
|
|||
|
||||
# Bitcell array
|
||||
self.bitcell_array = factory.create(module_type="bitcell_array",
|
||||
column_offset=1 + self.left_rbl,
|
||||
cols=self.column_size,
|
||||
rows=self.row_size)
|
||||
self.add_mod(self.bitcell_array)
|
||||
|
||||
# Replica bitlines
|
||||
self.replica_columns = {}
|
||||
for bit in range(self.left_rbl+self.right_rbl):
|
||||
for bit in range(self.left_rbl + self.right_rbl):
|
||||
# Creating left_rbl
|
||||
if bit<self.left_rbl:
|
||||
replica_bit = bit+1
|
||||
replica_bit = bit + 1
|
||||
# dummy column
|
||||
column_offset = self.left_rbl - bit
|
||||
# Creating right_rbl
|
||||
else:
|
||||
replica_bit = bit+self.row_size+1
|
||||
replica_bit = bit + self.row_size + 1
|
||||
# dummy column + replica column + bitcell colums
|
||||
column_offset = self.left_rbl - bit + self.row_size
|
||||
self.replica_columns[bit] = factory.create(module_type="replica_column",
|
||||
rows=self.row_size,
|
||||
left_rbl=self.left_rbl,
|
||||
right_rbl=self.right_rbl,
|
||||
column_offset=column_offset,
|
||||
replica_bit=replica_bit)
|
||||
self.add_mod(self.replica_columns[bit])
|
||||
|
||||
|
||||
# Dummy row
|
||||
self.dummy_row = factory.create(module_type="dummy_array",
|
||||
cols=self.column_size,
|
||||
rows=1,
|
||||
# dummy column + left replica column
|
||||
column_offset=1 + self.left_rbl,
|
||||
mirror=0)
|
||||
self.add_mod(self.dummy_row)
|
||||
|
||||
# Dummy col (mirror starting at first if odd replica+dummy rows)
|
||||
self.dummy_col = factory.create(module_type="dummy_array",
|
||||
cols=1,
|
||||
rows=self.row_size + self.extra_rows,
|
||||
mirror=(self.left_rbl+1)%2)
|
||||
self.add_mod(self.dummy_col)
|
||||
|
||||
|
||||
# If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps.
|
||||
try:
|
||||
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||
except AttributeError:
|
||||
end_caps_enabled = False
|
||||
|
||||
# Dummy Row or Col Cap, depending on bitcell array properties
|
||||
edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array")
|
||||
|
||||
self.edge_row = factory.create(module_type=edge_row_module_type,
|
||||
cols=self.column_size,
|
||||
rows=1,
|
||||
# dummy column + left replica column(s)
|
||||
column_offset=1 + self.left_rbl,
|
||||
mirror=0)
|
||||
self.add_mod(self.edge_row)
|
||||
|
||||
# Dummy Col or Row Cap, depending on bitcell array properties
|
||||
edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array")
|
||||
|
||||
self.edge_col_left = factory.create(module_type=edge_col_module_type,
|
||||
cols=1,
|
||||
column_offset=0,
|
||||
rows=self.row_size + self.extra_rows,
|
||||
mirror=(self.left_rbl + 1) % 2)
|
||||
self.add_mod(self.edge_col_left)
|
||||
|
||||
self.edge_col_right = factory.create(module_type=edge_col_module_type,
|
||||
cols=1,
|
||||
# dummy column
|
||||
# + left replica column(s)
|
||||
# + bitcell columns
|
||||
# + right replica column(s)
|
||||
column_offset = 1 + self.left_rbl + self.column_size + self.right_rbl,
|
||||
rows=self.row_size + self.extra_rows,
|
||||
mirror=(self.left_rbl + 1) %2)
|
||||
self.add_mod(self.edge_col_right)
|
||||
|
||||
def add_pins(self):
|
||||
self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names()
|
||||
self.bitcell_array_bl_names = self.bitcell_array.get_all_bitline_names()
|
||||
|
||||
self.bitcell_array_wl_names = [x for x in self.bitcell_array.pins if x.startswith("w")]
|
||||
self.bitcell_array_bl_names = [x for x in self.bitcell_array.pins if x.startswith("b")]
|
||||
|
||||
# These are the non-indexed names
|
||||
self.dummy_cell_wl_names = ["dummy_"+x for x in self.cell.get_all_wl_names()]
|
||||
self.dummy_cell_bl_names = ["dummy_"+x for x in self.cell.get_all_bitline_names()]
|
||||
# These are the non-indexed names
|
||||
self.dummy_cell_wl_names = ["dummy_" + x for x in self.cell.get_all_wl_names()]
|
||||
self.dummy_cell_bl_names = ["dummy_" + x for x in self.cell.get_all_bitline_names()]
|
||||
self.dummy_row_bl_names = self.bitcell_array_bl_names
|
||||
|
||||
# A dictionary because some ports may have nothing
|
||||
self.rbl_bl_names = {}
|
||||
self.rbl_br_names = {}
|
||||
self.rbl_wl_names = {}
|
||||
|
||||
|
||||
# Create the full WL names include dummy, replica, and regular bit cells
|
||||
self.replica_col_wl_names = []
|
||||
self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names])
|
||||
# Left port WLs (one dummy for each port when we allow >1 port)
|
||||
for port in range(self.left_rbl):
|
||||
# Make names for all RBLs
|
||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))]
|
||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
|
||||
# Keep track of the pin that is the RBL
|
||||
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
||||
self.replica_col_wl_names.extend(wl_names)
|
||||
# Regular WLs
|
||||
self.replica_col_wl_names.extend(self.bitcell_array_wl_names)
|
||||
# Right port WLs (one dummy for each port when we allow >1 port)
|
||||
for port in range(self.left_rbl,self.left_rbl+self.right_rbl):
|
||||
for port in range(self.left_rbl, self.left_rbl + self.right_rbl):
|
||||
# Make names for all RBLs
|
||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))]
|
||||
wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x), port) for x in range(len(self.cell.get_all_wl_names()))]
|
||||
# Keep track of the pin that is the RBL
|
||||
self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]]
|
||||
self.replica_col_wl_names.extend(wl_names)
|
||||
|
|
@ -159,14 +195,13 @@ class replica_bitcell_array(design.design):
|
|||
# Left/right dummy columns are connected identically to the replica column
|
||||
self.dummy_col_wl_names = self.replica_col_wl_names
|
||||
|
||||
|
||||
# Per port bitline names
|
||||
self.replica_bl_names = {}
|
||||
self.replica_wl_names = {}
|
||||
# Array of all port bitline names
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))]
|
||||
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))]
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x), port) for x in range(len(self.all_ports))]
|
||||
right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x), port) for x in range(len(self.all_ports))]
|
||||
# Keep track of the left pins that are the RBL
|
||||
self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]]
|
||||
self.rbl_br_names[port]=right_names[self.bitcell_ports[port]]
|
||||
|
|
@ -174,36 +209,33 @@ class replica_bitcell_array(design.design):
|
|||
bl_names = [x for t in zip(left_names, right_names) for x in t]
|
||||
self.replica_bl_names[port] = bl_names
|
||||
|
||||
wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()]
|
||||
#wl_names[port] = "rbl_wl{}".format(port)
|
||||
wl_names = ["rbl_{0}_{1}".format(x, port) for x in self.cell.get_all_wl_names()]
|
||||
self.replica_wl_names[port] = wl_names
|
||||
|
||||
|
||||
# External pins
|
||||
self.add_pin_list(self.bitcell_array_bl_names, "INOUT")
|
||||
# Need to sort by port order since dictionary values may not be in order
|
||||
bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())]
|
||||
br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())]
|
||||
for (bl_name,br_name) in zip(bl_names,br_names):
|
||||
self.add_pin(bl_name,"OUTPUT")
|
||||
self.add_pin(br_name,"OUTPUT")
|
||||
for (bl_name, br_name) in zip(bl_names, br_names):
|
||||
self.add_pin(bl_name, "OUTPUT")
|
||||
self.add_pin(br_name, "OUTPUT")
|
||||
self.add_pin_list(self.bitcell_array_wl_names, "INPUT")
|
||||
# Need to sort by port order since dictionary values may not be in order
|
||||
# Need to sort by port order since dictionary values may not be in order
|
||||
wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())]
|
||||
for pin_name in wl_names:
|
||||
self.add_pin(pin_name,"INPUT")
|
||||
self.add_pin(pin_name, "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
""" Create the module instances used in this design """
|
||||
|
||||
supplies = ["vdd", "gnd"]
|
||||
|
||||
|
||||
# Used for names/dimensions only
|
||||
self.cell = factory.create(module_type="bitcell")
|
||||
|
||||
|
||||
# Main array
|
||||
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
|
||||
mod=self.bitcell_array)
|
||||
|
|
@ -211,161 +243,162 @@ class replica_bitcell_array(design.design):
|
|||
|
||||
# Replica columns
|
||||
self.replica_col_inst = {}
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port),
|
||||
mod=self.replica_columns[port])
|
||||
mod=self.replica_columns[port])
|
||||
self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies)
|
||||
|
||||
|
||||
|
||||
# Dummy rows under the bitcell array (connected with with the replica cell wl)
|
||||
self.dummy_row_replica_inst = {}
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port),
|
||||
mod=self.dummy_row)
|
||||
self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies)
|
||||
|
||||
|
||||
# Top/bottom dummy rows
|
||||
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
|
||||
mod=self.dummy_row)
|
||||
self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies)
|
||||
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
|
||||
mod=self.dummy_row)
|
||||
self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies)
|
||||
|
||||
# Top/bottom dummy rows or col caps
|
||||
self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot",
|
||||
mod=self.edge_row)
|
||||
self.connect_inst(self.dummy_row_bl_names + [x + "_bot" for x in self.dummy_cell_wl_names] + supplies)
|
||||
self.dummy_row_top_inst=self.add_inst(name="dummy_row_top",
|
||||
mod=self.edge_row)
|
||||
self.connect_inst(self.dummy_row_bl_names + [x + "_top" for x in self.dummy_cell_wl_names] + supplies)
|
||||
|
||||
# Left/right Dummy columns
|
||||
self.dummy_col_left_inst=self.add_inst(name="dummy_col_left",
|
||||
mod=self.dummy_col)
|
||||
self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||
mod=self.edge_col_left)
|
||||
self.connect_inst([x + "_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||
self.dummy_col_right_inst=self.add_inst(name="dummy_col_right",
|
||||
mod=self.dummy_col)
|
||||
self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||
|
||||
mod=self.edge_col_right)
|
||||
self.connect_inst([x + "_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies)
|
||||
|
||||
|
||||
def create_layout(self):
|
||||
|
||||
self.height = (self.row_size+self.extra_rows)*self.dummy_row.height
|
||||
self.width = (self.column_size+self.extra_cols)*self.cell.width
|
||||
self.height = (self.row_size + self.extra_rows) * self.dummy_row.height
|
||||
self.width = (self.column_size + self.extra_cols) * self.cell.width
|
||||
|
||||
# This is a bitcell x bitcell offset to scale
|
||||
offset = vector(self.cell.width, self.cell.height)
|
||||
|
||||
self.bitcell_array_inst.place(offset=[0,0])
|
||||
|
||||
self.bitcell_array_inst.place(offset=[0, 0])
|
||||
|
||||
# To the left of the bitcell array
|
||||
for bit in range(self.left_rbl):
|
||||
self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1))
|
||||
self.replica_col_inst[bit].place(offset=offset.scale(-bit - 1, -self.left_rbl - 1))
|
||||
# To the right of the bitcell array
|
||||
for bit in range(self.right_rbl):
|
||||
self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr())
|
||||
|
||||
self.replica_col_inst[self.left_rbl + bit].place(offset=offset.scale(bit, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
|
||||
|
||||
# FIXME: These depend on the array size itself
|
||||
# Far top dummy row (first row above array is NOT flipped)
|
||||
flip_dummy = self.right_rbl%2
|
||||
self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(),
|
||||
flip_dummy = self.right_rbl % 2
|
||||
self.dummy_row_top_inst.place(offset=offset.scale(0, self.right_rbl + flip_dummy) + self.bitcell_array_inst.ul(),
|
||||
mirror="MX" if flip_dummy else "R0")
|
||||
# FIXME: These depend on the array size itself
|
||||
# Far bottom dummy row (first row below array IS flipped)
|
||||
flip_dummy = (self.left_rbl+1)%2
|
||||
self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy),
|
||||
flip_dummy = (self.left_rbl + 1) % 2
|
||||
self.dummy_row_bot_inst.place(offset=offset.scale(0, -self.left_rbl - 1 + flip_dummy),
|
||||
mirror="MX" if flip_dummy else "R0")
|
||||
# Far left dummy col
|
||||
self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1))
|
||||
self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl - 1, -self.left_rbl - 1))
|
||||
# Far right dummy col
|
||||
self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr())
|
||||
self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl, -self.left_rbl - 1) + self.bitcell_array_inst.lr())
|
||||
|
||||
# Replica dummy rows
|
||||
for bit in range(self.left_rbl):
|
||||
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2),
|
||||
mirror="R0" if bit%2 else "MX")
|
||||
self.dummy_row_replica_inst[bit].place(offset=offset.scale(0, -bit - bit % 2),
|
||||
mirror="R0" if bit % 2 else "MX")
|
||||
for bit in range(self.right_rbl):
|
||||
self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(),
|
||||
mirror="MX" if bit%2 else "R0")
|
||||
|
||||
self.dummy_row_replica_inst[self.left_rbl + bit].place(offset=offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul(),
|
||||
mirror="MX" if bit % 2 else "R0")
|
||||
|
||||
self.translate_all(offset.scale(-1 - self.left_rbl, -1 - self.left_rbl))
|
||||
|
||||
self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl))
|
||||
|
||||
self.add_layout_pins()
|
||||
|
||||
|
||||
self.add_boundary()
|
||||
|
||||
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add the layout pins """
|
||||
|
||||
# Main array wl and bl/br
|
||||
pin_names = self.bitcell_array.get_pin_names()
|
||||
for pin_name in pin_names:
|
||||
if pin_name.startswith("wl"):
|
||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_layout_pin(text=pin_name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=pin.height())
|
||||
elif pin_name.startswith("bl") or pin_name.startswith("br"):
|
||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_layout_pin(text=pin_name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(1,0),
|
||||
width=pin.width(),
|
||||
height=self.height)
|
||||
|
||||
for wl in self.bitcell_array_wl_names:
|
||||
if wl in pin_name:
|
||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_layout_pin(text=pin_name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(0, 1),
|
||||
width=self.width,
|
||||
height=pin.height())
|
||||
for bitline in self.bitcell_array_bl_names:
|
||||
if bitline in pin_name:
|
||||
pin_list = self.bitcell_array_inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_layout_pin(text=pin_name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(1, 0),
|
||||
width=pin.width(),
|
||||
height=self.height)
|
||||
|
||||
# Replica wordlines
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
inst = self.replica_col_inst[port]
|
||||
for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]):
|
||||
for (pin_name, wl_name) in zip(self.cell.get_all_wl_names(), self.replica_wl_names[port]):
|
||||
# +1 for dummy row
|
||||
pin_bit = port+1
|
||||
# +row_size if above the array
|
||||
pin_bit = port + 1
|
||||
# +row_size if above the array
|
||||
if port>=self.left_rbl:
|
||||
pin_bit += self.row_size
|
||||
|
||||
|
||||
pin_name += "_{}".format(pin_bit)
|
||||
pin = inst.get_pin(pin_name)
|
||||
if wl_name in self.rbl_wl_names.values():
|
||||
self.add_layout_pin(text=wl_name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(0,1),
|
||||
offset=pin.ll().scale(0, 1),
|
||||
width=self.width,
|
||||
height=pin.height())
|
||||
|
||||
|
||||
# Replica bitlines
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
inst = self.replica_col_inst[port]
|
||||
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]):
|
||||
for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]):
|
||||
pin = inst.get_pin(pin_name)
|
||||
if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names:
|
||||
name = bl_name
|
||||
else:
|
||||
name = "rbl_{0}_{1}".format(pin_name,port)
|
||||
name = "rbl_{0}_{1}".format(pin_name, port)
|
||||
self.add_layout_pin(text=name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll().scale(1,0),
|
||||
offset=pin.ll().scale(1, 0),
|
||||
width=pin.width(),
|
||||
height=self.height)
|
||||
|
||||
|
||||
for pin_name in ["vdd","gnd"]:
|
||||
for inst in self.insts:
|
||||
# vdd/gnd are only connected in the perimeter cells
|
||||
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
|
||||
supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
|
||||
self.dummy_row_top_inst, self.dummy_row_bot_inst]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for inst in supply_insts:
|
||||
pin_list = inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer)
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
directions=("V", "V"),
|
||||
start_layer=pin.layer)
|
||||
|
||||
for inst in list(self.replica_col_inst.values()):
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_rbl_wl_name(self, port):
|
||||
""" Return the WL for the given RBL port """
|
||||
return self.rbl_wl_names[port]
|
||||
|
||||
|
||||
def get_rbl_bl_name(self, port):
|
||||
""" Return the BL for the given RBL port """
|
||||
return self.rbl_bl_names[port]
|
||||
|
|
@ -376,19 +409,17 @@ class replica_bitcell_array(design.design):
|
|||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Power of Bitcell array and bitline in nW."""
|
||||
from tech import drc, parameter
|
||||
|
||||
# Dynamic Power from Bitline
|
||||
bl_wire = self.gen_bl_wire()
|
||||
cell_load = 2 * bl_wire.return_input_cap()
|
||||
cell_load = 2 * bl_wire.return_input_cap()
|
||||
bl_swing = OPTS.rbl_delay_percentage
|
||||
freq = spice["default_event_frequency"]
|
||||
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
||||
|
||||
#Calculate the bitcell power which currently only includes leakage
|
||||
|
||||
# Calculate the bitcell power which currently only includes leakage
|
||||
cell_power = self.cell.analytical_power(corner, load)
|
||||
|
||||
#Leakage power grows with entire array and bitlines.
|
||||
|
||||
# Leakage power grows with entire array and bitlines.
|
||||
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
|
||||
cell_power.leakage * self.column_size * self.row_size)
|
||||
return total_power
|
||||
|
|
@ -399,13 +430,13 @@ class replica_bitcell_array(design.design):
|
|||
else:
|
||||
height = self.height
|
||||
bl_pos = 0
|
||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
||||
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
|
||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||
return bl_wire
|
||||
|
||||
def get_wordline_cin(self):
|
||||
"""Get the relative input capacitance from the wordline connections in all the bitcell"""
|
||||
#A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
# A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns
|
||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||
total_cin = bitcell_wl_cin * self.column_size
|
||||
return total_cin
|
||||
|
|
@ -413,13 +444,13 @@ class replica_bitcell_array(design.design):
|
|||
def graph_exclude_bits(self, targ_row, targ_col):
|
||||
"""Excludes bits in column from being added to graph except target"""
|
||||
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
|
||||
|
||||
|
||||
def graph_exclude_replica_col_bits(self):
|
||||
"""Exclude all replica/dummy cells in the replica columns except the replica bit."""
|
||||
|
||||
for port in range(self.left_rbl+self.right_rbl):
|
||||
|
||||
for port in range(self.left_rbl + self.right_rbl):
|
||||
self.replica_columns[port].exclude_all_but_replica()
|
||||
|
||||
def get_cell_name(self, inst_name, row, col):
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col)
|
||||
return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col)
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# Copyright (c) 2016-2019 Regents of the University of California
|
||||
# All rights reserved.
|
||||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
import contact
|
||||
from tech import cell_properties
|
||||
from sram_factory import factory
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class replica_column(design.design):
|
||||
"""
|
||||
Generate a replica bitline column for the replica array.
|
||||
Rows is the total number of rows i the main array.
|
||||
Left_rbl and right_rbl are the number of left and right replica bitlines.
|
||||
Replica bit specifies which replica column this is (to determine where to put the
|
||||
Replica bit specifies which replica column this is (to determine where to put the
|
||||
replica cell.
|
||||
"""
|
||||
|
||||
def __init__(self, name, rows, left_rbl, right_rbl, replica_bit):
|
||||
def __init__(self, name, rows, left_rbl, right_rbl, replica_bit,
|
||||
column_offset=0):
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.rows = rows
|
||||
|
|
@ -28,24 +29,30 @@ class replica_column(design.design):
|
|||
self.right_rbl = right_rbl
|
||||
self.replica_bit = replica_bit
|
||||
# left, right, regular rows plus top/bottom dummy cells
|
||||
self.total_size = self.left_rbl+rows+self.right_rbl+2
|
||||
|
||||
debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.")
|
||||
debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1,
|
||||
"Replica bit cannot be in the regular array.")
|
||||
self.total_size = self.left_rbl + rows + self.right_rbl + 2
|
||||
self.column_offset = column_offset
|
||||
|
||||
debug.check(replica_bit != 0 and replica_bit != rows,
|
||||
"Replica bit cannot be the dummy row.")
|
||||
debug.check(replica_bit <= left_rbl or replica_bit >= self.total_size - right_rbl - 1,
|
||||
"Replica bit cannot be in the regular array.")
|
||||
if OPTS.tech_name == "sky130":
|
||||
debug.check(rows % 2 == 0 and (left_rbl + 1) % 2 == 0,
|
||||
"sky130 currently requires rows to be even and to start with X mirroring"
|
||||
+ " (left_rbl must be odd) for LVS.")
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
self.add_pins()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
self.height = self.total_size*self.cell.height
|
||||
self.width = self.cell.width
|
||||
self.height = self.total_size * self.cell.height
|
||||
self.width = self.cell.width
|
||||
|
||||
self.place_instances()
|
||||
self.add_layout_pins()
|
||||
|
|
@ -53,109 +60,170 @@ class replica_column(design.design):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
|
||||
|
||||
for bl_name in self.cell.get_all_bitline_names():
|
||||
# In the replica column, these are only outputs!
|
||||
self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT")
|
||||
self.add_pin("{0}_{1}".format(bl_name, 0), "OUTPUT")
|
||||
|
||||
for row in range(self.total_size):
|
||||
for wl_name in self.cell.get_all_wl_names():
|
||||
self.add_pin("{0}_{1}".format(wl_name,row), "INPUT")
|
||||
|
||||
self.add_pin("{0}_{1}".format(wl_name, row), "INPUT")
|
||||
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
self.replica_cell = factory.create(module_type="replica_bitcell")
|
||||
self.replica_cell = factory.create(module_type="replica_{}".format(OPTS.bitcell))
|
||||
self.add_mod(self.replica_cell)
|
||||
self.dummy_cell = factory.create(module_type="dummy_bitcell")
|
||||
self.dummy_cell = factory.create(module_type="dummy_{}".format(OPTS.bitcell))
|
||||
self.add_mod(self.dummy_cell)
|
||||
try:
|
||||
edge_module_type = ("col_cap" if cell_properties.bitcell.end_caps else "dummy")
|
||||
except AttributeError:
|
||||
edge_module_type = "dummy"
|
||||
self.edge_cell = factory.create(module_type=edge_module_type + "_" + OPTS.bitcell)
|
||||
self.add_mod(self.edge_cell)
|
||||
# Used for pin names only
|
||||
self.cell = factory.create(module_type="bitcell")
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
|
||||
try:
|
||||
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||
except AttributeError:
|
||||
end_caps_enabled = False
|
||||
|
||||
self.cell_inst = {}
|
||||
for row in range(self.total_size):
|
||||
name="rbc_{0}".format(row)
|
||||
# Top/bottom cell are always dummy cells.
|
||||
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
|
||||
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
||||
if (row>self.left_rbl and row<self.total_size-self.right_rbl-1):
|
||||
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
|
||||
if (row > self.left_rbl and row < self.total_size - self.right_rbl - 1):
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.replica_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||
elif row==self.replica_bit:
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.replica_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||
elif (row == 0 or row == self.total_size - 1):
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.edge_cell)
|
||||
if end_caps_enabled:
|
||||
self.connect_inst(self.get_bitcell_pins_col_cap(0, row))
|
||||
else:
|
||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||
else:
|
||||
self.cell_inst[row]=self.add_inst(name=name,
|
||||
mod=self.dummy_cell)
|
||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||
|
||||
def place_instances(self):
|
||||
self.connect_inst(self.get_bitcell_pins(0, row))
|
||||
|
||||
def place_instances(self):
|
||||
from tech import cell_properties
|
||||
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
|
||||
# so that we will start with mirroring rather than not mirroring
|
||||
rbl_offset = (self.left_rbl+1)%2
|
||||
|
||||
rbl_offset = (self.left_rbl + 1) %2
|
||||
|
||||
# if our bitcells are mirrored on the y axis, check if we are in global
|
||||
# column that needs to be flipped.
|
||||
dir_y = False
|
||||
xoffset = 0
|
||||
if cell_properties.bitcell.mirror.y and self.column_offset % 2:
|
||||
dir_y = True
|
||||
xoffset = self.replica_cell.width
|
||||
|
||||
for row in range(self.total_size):
|
||||
name = "bit_r{0}_{1}".format(row,"rbl")
|
||||
offset = vector(0,self.cell.height*(row+(row+rbl_offset)%2))
|
||||
if (row+rbl_offset)%2:
|
||||
# name = "bit_r{0}_{1}".format(row, "rbl")
|
||||
dir_x = cell_properties.bitcell.mirror.x and (row + rbl_offset) % 2
|
||||
|
||||
offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2))
|
||||
|
||||
if dir_x and dir_y:
|
||||
dir_key = "XY"
|
||||
elif dir_x:
|
||||
dir_key = "MX"
|
||||
elif dir_y:
|
||||
dir_key = "MY"
|
||||
else:
|
||||
dir_key = "R0"
|
||||
dir_key = ""
|
||||
|
||||
self.cell_inst[row].place(offset=offset,
|
||||
mirror=dir_key)
|
||||
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add the layout pins """
|
||||
|
||||
|
||||
for bl_name in self.cell.get_all_bitline_names():
|
||||
bl_pin = self.cell_inst[0].get_pin(bl_name)
|
||||
self.add_layout_pin(text=bl_name,
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
layer=bl_pin.layer,
|
||||
offset=bl_pin.ll().scale(1, 0),
|
||||
width=bl_pin.width(),
|
||||
height=self.height)
|
||||
|
||||
for row in range(self.total_size):
|
||||
try:
|
||||
end_caps_enabled = cell_properties.bitcell.end_caps
|
||||
except AttributeError:
|
||||
end_caps_enabled = False
|
||||
|
||||
if end_caps_enabled:
|
||||
row_range_max = self.total_size - 1
|
||||
row_range_min = 1
|
||||
else:
|
||||
row_range_max = self.total_size
|
||||
row_range_min = 0
|
||||
|
||||
for row in range(row_range_min, row_range_max):
|
||||
for wl_name in self.cell.get_all_wl_names():
|
||||
wl_pin = self.cell_inst[row].get_pin(wl_name)
|
||||
self.add_layout_pin(text="{0}_{1}".format(wl_name,row),
|
||||
layer="metal1",
|
||||
offset=wl_pin.ll().scale(0,1),
|
||||
self.add_layout_pin(text="{0}_{1}".format(wl_name, row),
|
||||
layer=wl_pin.layer,
|
||||
offset=wl_pin.ll().scale(0, 1),
|
||||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For every second row and column, add a via for gnd and vdd
|
||||
for row in range(self.total_size):
|
||||
inst = self.cell_inst[row]
|
||||
# Supplies are only connected in the ends
|
||||
for (index, inst) in self.cell_inst.items():
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
||||
self.copy_power_pins(inst, pin_name)
|
||||
else:
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell,
|
||||
""" Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array """
|
||||
|
||||
bitcell_pins = []
|
||||
|
||||
|
||||
pin_names = self.cell.get_all_bitline_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(col))
|
||||
bitcell_pins.append(pin + "_{0}".format(col))
|
||||
pin_names = self.cell.get_all_wl_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(row))
|
||||
bitcell_pins.append(pin + "_{0}".format(row))
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
|
||||
|
||||
return bitcell_pins
|
||||
|
||||
|
||||
def get_bitcell_pins_col_cap(self, col, row):
|
||||
""" Creates a list of connections in the bitcell,
|
||||
indexed by column and row, for instance use in bitcell_array """
|
||||
|
||||
bitcell_pins = []
|
||||
|
||||
pin_names = self.cell.get_all_bitline_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin + "_{0}".format(col))
|
||||
bitcell_pins.append("vdd")
|
||||
|
||||
return bitcell_pins
|
||||
|
||||
def exclude_all_but_replica(self):
|
||||
"""Excludes all bits except the replica cell (self.replica_bit)."""
|
||||
|
||||
|
||||
for row, cell in self.cell_inst.items():
|
||||
if row != self.replica_bit:
|
||||
self.graph_inst_exclude.add(cell)
|
||||
|
|
|
|||
|
|
@ -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 debug
|
||||
import utils
|
||||
from tech import GDS,layer, parameter,drc
|
||||
from tech import GDS, layer, parameter, drc
|
||||
from tech import cell_properties as props
|
||||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
|
||||
class sense_amp(design.design):
|
||||
"""
|
||||
This module implements the single sense amp cell used in the design. It
|
||||
|
|
@ -18,11 +21,33 @@ class sense_amp(design.design):
|
|||
the technology library.
|
||||
Sense amplifier to read a pair of bit-lines.
|
||||
"""
|
||||
|
||||
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
||||
pin_names = [props.sense_amp.pin.bl,
|
||||
props.sense_amp.pin.br,
|
||||
props.sense_amp.pin.dout,
|
||||
props.sense_amp.pin.en,
|
||||
props.sense_amp.pin.vdd,
|
||||
props.sense_amp.pin.gnd]
|
||||
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||
if not OPTS.netlist_only:
|
||||
(width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||
else:
|
||||
(width, height) = (0, 0)
|
||||
pin_map = []
|
||||
|
||||
def get_bl_names(self):
|
||||
return props.sense_amp.pin.bl
|
||||
|
||||
def get_br_names(self):
|
||||
return props.sense_amp.pin.br
|
||||
|
||||
@property
|
||||
def dout_name(self):
|
||||
return props.sense_amp.pin.dout
|
||||
|
||||
@property
|
||||
def en_name(self):
|
||||
return props.sense_amp.pin.en
|
||||
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
|
|
@ -37,42 +62,41 @@ class sense_amp(design.design):
|
|||
|
||||
# FIXME: This input load will be applied to both the s_en timing and bitline timing.
|
||||
|
||||
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
from tech import spice, parameter
|
||||
# Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
from tech import spice
|
||||
# Default is 8x. Per Samira and Hodges-Jackson book:
|
||||
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
|
||||
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||
return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff
|
||||
bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||
return spice["min_tx_drain_c"] * bitline_pmos_size # ff
|
||||
|
||||
def get_stage_effort(self, load):
|
||||
#Delay of the sense amp will depend on the size of the amp and the output load.
|
||||
# Delay of the sense amp will depend on the size of the amp and the output load.
|
||||
parasitic_delay = 1
|
||||
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")
|
||||
sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx")
|
||||
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx")
|
||||
sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx")
|
||||
cc_inv_cin = cin
|
||||
return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False)
|
||||
return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Returns dynamic and leakage power. Results in nW"""
|
||||
#Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
||||
# Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
||||
total_power = self.return_power()
|
||||
return total_power
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the relative capacitance of sense amp enable gate cin"""
|
||||
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx")
|
||||
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
|
||||
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||
return 2*pmos_cin + nmos_cin
|
||||
pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx")
|
||||
nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx")
|
||||
# sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||
return 2 * pmos_cin + nmos_cin
|
||||
|
||||
def get_enable_name(self):
|
||||
"""Returns name used for enable net"""
|
||||
#FIXME: A better programmatic solution to designate pins
|
||||
enable_name = "en"
|
||||
# FIXME: A better programmatic solution to designate pins
|
||||
enable_name = self.en_name
|
||||
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
|
||||
return enable_name
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
|
|||
|
|
@ -13,26 +13,54 @@ import debug
|
|||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
|
||||
class sense_amp_array(design.design):
|
||||
"""
|
||||
Array of sense amplifiers to read the bitlines through the column mux.
|
||||
Dynamically generated sense amp array for all bitlines.
|
||||
"""
|
||||
|
||||
def __init__(self, name, word_size, words_per_row):
|
||||
def __init__(self, name, word_size, words_per_row, num_spare_cols=None, column_offset=0):
|
||||
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0}".format(self.name))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
|
||||
self.word_size = word_size
|
||||
self.words_per_row = words_per_row
|
||||
if not num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
self.num_spare_cols = num_spare_cols
|
||||
|
||||
self.column_offset = column_offset
|
||||
self.row_size = self.word_size * self.words_per_row
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.en_layer = "m3"
|
||||
else:
|
||||
self.en_layer = "m1"
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def get_bl_name(self):
|
||||
bl_name = "bl"
|
||||
return bl_name
|
||||
|
||||
def get_br_name(self):
|
||||
br_name = "br"
|
||||
return br_name
|
||||
|
||||
@property
|
||||
def data_name(self):
|
||||
return "data"
|
||||
|
||||
@property
|
||||
def en_name(self):
|
||||
return "en"
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
|
|
@ -41,11 +69,11 @@ class sense_amp_array(design.design):
|
|||
|
||||
def create_layout(self):
|
||||
self.height = self.amp.height
|
||||
|
||||
|
||||
if self.bitcell.width > self.amp.width:
|
||||
self.width = self.bitcell.width * self.word_size * self.words_per_row
|
||||
self.width = self.bitcell.width * (self.word_size * self.words_per_row + self.num_spare_cols)
|
||||
else:
|
||||
self.width = self.amp.width * self.word_size * self.words_per_row
|
||||
self.width = self.amp.width * (self.word_size * self.words_per_row + self.num_spare_cols)
|
||||
|
||||
self.place_sense_amp_array()
|
||||
self.add_layout_pins()
|
||||
|
|
@ -54,105 +82,126 @@ class sense_amp_array(design.design):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
for i in range(0,self.word_size):
|
||||
self.add_pin("data_{0}".format(i), "OUTPUT")
|
||||
self.add_pin("bl_{0}".format(i), "INPUT")
|
||||
self.add_pin("br_{0}".format(i), "INPUT")
|
||||
self.add_pin("en", "INPUT")
|
||||
for i in range(0, self.word_size + self.num_spare_cols):
|
||||
self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT")
|
||||
self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT")
|
||||
self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT")
|
||||
self.add_pin(self.en_name, "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
self.amp = factory.create(module_type="sense_amp")
|
||||
|
||||
|
||||
self.add_mod(self.amp)
|
||||
|
||||
# This is just used for measurements,
|
||||
# so don't add the module
|
||||
self.bitcell = factory.create(module_type="bitcell")
|
||||
|
||||
|
||||
def create_sense_amp_array(self):
|
||||
self.local_insts = []
|
||||
for i in range(0,self.word_size):
|
||||
|
||||
for i in range(0, self.word_size + self.num_spare_cols):
|
||||
name = "sa_d{0}".format(i)
|
||||
self.local_insts.append(self.add_inst(name=name,
|
||||
mod=self.amp))
|
||||
self.connect_inst(["bl_{0}".format(i),
|
||||
"br_{0}".format(i),
|
||||
"data_{0}".format(i),
|
||||
"en", "vdd", "gnd"])
|
||||
self.connect_inst([self.get_bl_name() + "_{0}".format(i),
|
||||
self.get_br_name() + "_{0}".format(i),
|
||||
self.data_name + "_{0}".format(i),
|
||||
self.en_name, "vdd", "gnd"])
|
||||
|
||||
def place_sense_amp_array(self):
|
||||
|
||||
if self.bitcell.width > self.amp.width:
|
||||
amp_spacing = self.bitcell.width * self.words_per_row
|
||||
else:
|
||||
amp_spacing = self.amp.width * self.words_per_row
|
||||
for i in range(0,self.word_size):
|
||||
amp_position = vector(amp_spacing * i, 0)
|
||||
self.local_insts[i].place(amp_position)
|
||||
from tech import cell_properties
|
||||
|
||||
for i in range(0, self.row_size, self.words_per_row):
|
||||
index = int(i / self.words_per_row)
|
||||
xoffset = i * self.bitcell.width
|
||||
|
||||
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
xoffset = xoffset + self.amp.width
|
||||
else:
|
||||
mirror = ""
|
||||
|
||||
amp_position = vector(xoffset, 0)
|
||||
self.local_insts[index].place(offset=amp_position, mirror=mirror)
|
||||
|
||||
# place spare sense amps (will share the same enable as regular sense amps)
|
||||
for i in range(0, self.num_spare_cols):
|
||||
index = self.word_size + i
|
||||
xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width
|
||||
|
||||
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
xoffset = xoffset + self.amp.width
|
||||
else:
|
||||
mirror = ""
|
||||
|
||||
amp_position = vector(xoffset, 0)
|
||||
self.local_insts[index].place(offset=amp_position, mirror=mirror)
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
for i in range(len(self.local_insts)):
|
||||
inst = self.local_insts[i]
|
||||
|
||||
gnd_pos = inst.get_pin("gnd").center()
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=gnd_pos)
|
||||
self.add_layout_pin_rect_center(text="gnd",
|
||||
layer="metal3",
|
||||
offset=gnd_pos)
|
||||
vdd_pos = inst.get_pin("vdd").center()
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=vdd_pos)
|
||||
self.add_layout_pin_rect_center(text="vdd",
|
||||
layer="metal3",
|
||||
offset=vdd_pos)
|
||||
|
||||
bl_pin = inst.get_pin("bl")
|
||||
br_pin = inst.get_pin("br")
|
||||
dout_pin = inst.get_pin("dout")
|
||||
|
||||
self.add_layout_pin(text="bl_{0}".format(i),
|
||||
layer="metal2",
|
||||
for gnd_pin in inst.get_pins("gnd"):
|
||||
self.add_power_pin(name="gnd",
|
||||
loc=gnd_pin.center(),
|
||||
start_layer=gnd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
|
||||
for vdd_pin in inst.get_pins("vdd"):
|
||||
self.add_power_pin(name="vdd",
|
||||
loc=vdd_pin.center(),
|
||||
start_layer=vdd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
|
||||
bl_pin = inst.get_pin(inst.mod.get_bl_names())
|
||||
br_pin = inst.get_pin(inst.mod.get_br_names())
|
||||
dout_pin = inst.get_pin(inst.mod.dout_name)
|
||||
|
||||
self.add_layout_pin(text=self.get_bl_name() + "_{0}".format(i),
|
||||
layer=bl_pin.layer,
|
||||
offset=bl_pin.ll(),
|
||||
width=bl_pin.width(),
|
||||
height=bl_pin.height())
|
||||
self.add_layout_pin(text="br_{0}".format(i),
|
||||
layer="metal2",
|
||||
self.add_layout_pin(text=self.get_br_name() + "_{0}".format(i),
|
||||
layer=br_pin.layer,
|
||||
offset=br_pin.ll(),
|
||||
width=br_pin.width(),
|
||||
height=br_pin.height())
|
||||
|
||||
self.add_layout_pin(text="data_{0}".format(i),
|
||||
layer="metal2",
|
||||
|
||||
self.add_layout_pin(text=self.data_name + "_{0}".format(i),
|
||||
layer=dout_pin.layer,
|
||||
offset=dout_pin.ll(),
|
||||
width=dout_pin.width(),
|
||||
height=dout_pin.height())
|
||||
|
||||
|
||||
|
||||
def route_rails(self):
|
||||
# add sclk rail across entire array
|
||||
sclk_offset = self.amp.get_pin("en").ll().scale(0,1)
|
||||
self.add_layout_pin(text="en",
|
||||
layer="metal1",
|
||||
offset=sclk_offset,
|
||||
width=self.width,
|
||||
height=drc("minwidth_metal1"))
|
||||
# Add enable across the array
|
||||
en_pin = self.amp.get_pin(self.amp.en_name)
|
||||
start_offset = en_pin.lc().scale(0, 1)
|
||||
end_offset = start_offset + vector(self.width, 0)
|
||||
self.add_layout_pin_segment_center(text=self.en_name,
|
||||
layer=self.en_layer,
|
||||
start=start_offset,
|
||||
end=end_offset)
|
||||
for inst in self.local_insts:
|
||||
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||
to_layer=self.en_layer,
|
||||
offset=inst.get_pin(self.amp.en_name).center())
|
||||
|
||||
def input_load(self):
|
||||
return self.amp.input_load()
|
||||
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the relative capacitance of all the sense amp enable connections in the array"""
|
||||
sense_amp_en_cin = self.amp.get_en_cin()
|
||||
return sense_amp_en_cin * self.word_size
|
||||
|
||||
|
||||
def get_drain_cin(self):
|
||||
"""Get the relative capacitance of the drain of the PMOS isolation TX"""
|
||||
from tech import parameter
|
||||
#Bitcell drain load being used to estimate PMOS drain load
|
||||
# Bitcell drain load being used to estimate PMOS drain load
|
||||
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
||||
return drain_load
|
||||
|
|
|
|||
|
|
@ -5,56 +5,77 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from math import log
|
||||
import design
|
||||
import contact
|
||||
from tech import drc
|
||||
import debug
|
||||
import math
|
||||
from tech import layer, preferred_directions
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
|
||||
class single_level_column_mux_array(design.design):
|
||||
"""
|
||||
Dynamically generated column mux array.
|
||||
Array of column mux to read the bitlines through the 6T.
|
||||
"""
|
||||
|
||||
def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"):
|
||||
def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br", column_offset=0):
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0}".format(self.name))
|
||||
self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br))
|
||||
|
||||
|
||||
self.columns = columns
|
||||
self.word_size = word_size
|
||||
self.words_per_row = int(self.columns / self.word_size)
|
||||
self.bitcell_bl = bitcell_bl
|
||||
self.bitcell_br = bitcell_br
|
||||
|
||||
self.column_offset = column_offset
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.sel_layer = "m3"
|
||||
self.sel_pitch = self.m3_pitch
|
||||
self.bitline_layer = "m1"
|
||||
else:
|
||||
self.sel_layer = "m1"
|
||||
self.sel_pitch = self.m2_pitch
|
||||
self.bitline_layer = "m2"
|
||||
|
||||
if preferred_directions[self.sel_layer] == "V":
|
||||
self.via_directions = ("H", "H")
|
||||
else:
|
||||
self.via_directions = "pref"
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def get_bl_name(self):
|
||||
bl_name = self.mux.get_bl_names()
|
||||
return bl_name
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
br_name = self.mux.get_br_names()
|
||||
return br_name
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
self.add_pins()
|
||||
self.create_array()
|
||||
|
||||
|
||||
def create_layout(self):
|
||||
self.setup_layout_constants()
|
||||
self.place_array()
|
||||
self.add_routing()
|
||||
# Find the highest shapes to determine height before adding well
|
||||
highest = self.find_highest_coords()
|
||||
self.height = highest.y
|
||||
self.height = highest.y
|
||||
self.add_layout_pins()
|
||||
self.add_enclosure(self.mux_inst, "pwell")
|
||||
|
||||
if "pwell" in layer:
|
||||
self.add_enclosure(self.mux_inst, "pwell")
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
for i in range(self.columns):
|
||||
self.add_pin("bl_{}".format(i))
|
||||
|
|
@ -66,23 +87,19 @@ class single_level_column_mux_array(design.design):
|
|||
self.add_pin("br_out_{}".format(i))
|
||||
self.add_pin("gnd")
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
self.mux = factory.create(module_type="single_level_column_mux",
|
||||
bitcell_bl=self.bitcell_bl,
|
||||
bitcell_br=self.bitcell_br)
|
||||
self.add_mod(self.mux)
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
self.column_addr_size = num_of_inputs = int(self.words_per_row / 2)
|
||||
self.column_addr_size = int(self.words_per_row / 2)
|
||||
self.width = self.columns * self.mux.width
|
||||
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
|
||||
# one extra route pitch is to space from the sense amp
|
||||
self.route_height = (self.words_per_row + 3)*self.m1_pitch
|
||||
|
||||
self.route_height = (self.words_per_row + 3) * self.sel_pitch
|
||||
|
||||
|
||||
def create_array(self):
|
||||
self.mux_inst = []
|
||||
# For every column, add a pass gate
|
||||
|
|
@ -90,60 +107,67 @@ class single_level_column_mux_array(design.design):
|
|||
name = "XMUX{0}".format(col_num)
|
||||
self.mux_inst.append(self.add_inst(name=name,
|
||||
mod=self.mux))
|
||||
|
||||
|
||||
self.connect_inst(["bl_{}".format(col_num),
|
||||
"br_{}".format(col_num),
|
||||
"bl_out_{}".format(int(col_num/self.words_per_row)),
|
||||
"br_out_{}".format(int(col_num/self.words_per_row)),
|
||||
"bl_out_{}".format(int(col_num / self.words_per_row)),
|
||||
"br_out_{}".format(int(col_num / self.words_per_row)),
|
||||
"sel_{}".format(col_num % self.words_per_row),
|
||||
"gnd"])
|
||||
|
||||
def place_array(self):
|
||||
from tech import cell_properties
|
||||
# For every column, add a pass gate
|
||||
for col_num in range(self.columns):
|
||||
name = "XMUX{0}".format(col_num)
|
||||
x_off = vector(col_num * self.mux.width, self.route_height)
|
||||
self.mux_inst[col_num].place(x_off)
|
||||
|
||||
xoffset = col_num * self.mux.width
|
||||
if cell_properties.bitcell.mirror.y and (col_num + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
xoffset = xoffset + self.mux.width
|
||||
else:
|
||||
mirror = ""
|
||||
|
||||
offset = vector(xoffset, self.route_height)
|
||||
self.mux_inst[col_num].place(offset=offset, mirror=mirror)
|
||||
|
||||
def add_layout_pins(self):
|
||||
""" Add the pins after we determine the height. """
|
||||
# For every column, add a pass gate
|
||||
for col_num in range(self.columns):
|
||||
mux_inst = self.mux_inst[col_num]
|
||||
offset = mux_inst.get_pin("bl").ll()
|
||||
bl_pin = mux_inst.get_pin("bl")
|
||||
offset = bl_pin.ll()
|
||||
self.add_layout_pin(text="bl_{}".format(col_num),
|
||||
layer="metal2",
|
||||
layer=bl_pin.layer,
|
||||
offset=offset,
|
||||
height=self.height-offset.y)
|
||||
height=self.height - offset.y)
|
||||
|
||||
offset = mux_inst.get_pin("br").ll()
|
||||
br_pin = mux_inst.get_pin("br")
|
||||
offset = br_pin.ll()
|
||||
self.add_layout_pin(text="br_{}".format(col_num),
|
||||
layer="metal2",
|
||||
layer=br_pin.layer,
|
||||
offset=offset,
|
||||
height=self.height-offset.y)
|
||||
height=self.height - offset.y)
|
||||
|
||||
for inst in self.mux_inst:
|
||||
self.copy_layout_pin(inst, "gnd")
|
||||
|
||||
|
||||
def add_routing(self):
|
||||
self.add_horizontal_input_rail()
|
||||
self.add_vertical_poly_rail()
|
||||
self.route_bitlines()
|
||||
|
||||
def add_horizontal_input_rail(self):
|
||||
""" Create address input rails on M1 below the mux transistors """
|
||||
""" Create address input rails below the mux transistors """
|
||||
for j in range(self.words_per_row):
|
||||
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
|
||||
offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch)
|
||||
self.add_layout_pin(text="sel_{}".format(j),
|
||||
layer="metal1",
|
||||
layer=self.sel_layer,
|
||||
offset=offset,
|
||||
width=self.mux.width * self.columns)
|
||||
|
||||
def add_vertical_poly_rail(self):
|
||||
""" Connect the poly to the address rails """
|
||||
|
||||
|
||||
# Offset to the first transistor gate in the pass gate
|
||||
for col in range(self.columns):
|
||||
# which select bit should this column connect to depends on the position in the word
|
||||
|
|
@ -151,68 +175,65 @@ class single_level_column_mux_array(design.design):
|
|||
# Add the column x offset to find the right select bit
|
||||
gate_offset = self.mux_inst[col].get_pin("sel").bc()
|
||||
# height to connect the gate to the correct horizontal row
|
||||
sel_height = self.get_pin("sel_{}".format(sel_index)).by()
|
||||
# sel_height = self.get_pin("sel_{}".format(sel_index)).by()
|
||||
# use the y offset from the sel pin and the x offset from the gate
|
||||
offset = vector(gate_offset.x,self.get_pin("sel_{}".format(sel_index)).cy())
|
||||
# Add the poly contact with a shift to account for the rotation
|
||||
self.add_via_center(layers=("metal1", "contact", "poly"),
|
||||
offset=offset)
|
||||
offset = vector(gate_offset.x,
|
||||
self.get_pin("sel_{}".format(sel_index)).cy())
|
||||
self.add_via_stack_center(from_layer="poly",
|
||||
to_layer=self.sel_layer,
|
||||
offset=offset,
|
||||
directions=self.via_directions)
|
||||
self.add_path("poly", [offset, gate_offset])
|
||||
|
||||
def route_bitlines(self):
|
||||
""" Connect the output bit-lines to form the appropriate width mux """
|
||||
for j in range(self.columns):
|
||||
bl_offset = self.mux_inst[j].get_pin("bl_out").bc()
|
||||
br_offset = self.mux_inst[j].get_pin("br_out").bc()
|
||||
|
||||
bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch)
|
||||
br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch)
|
||||
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
|
||||
br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
|
||||
|
||||
bl_out_offset_end = bl_out_offset + vector(0,self.route_height)
|
||||
br_out_offset_end = br_out_offset + vector(0,self.route_height)
|
||||
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||
br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||
|
||||
if (j % self.words_per_row) == 0:
|
||||
# Create the metal1 to connect the n-way mux output from the pass gate
|
||||
# These will be located below the select lines. Yes, these are M2 width
|
||||
# to ensure vias are enclosed and M1 min width rules.
|
||||
width = self.m2_width + self.mux.width * (self.words_per_row - 1)
|
||||
self.add_path("metal1", [bl_out_offset, bl_out_offset+vector(width,0)])
|
||||
self.add_path("metal1", [br_out_offset, br_out_offset+vector(width,0)])
|
||||
# Add the horizontal wires for the first bit
|
||||
if j % self.words_per_row == 0:
|
||||
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
|
||||
br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
|
||||
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||
|
||||
self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
|
||||
self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
|
||||
|
||||
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
|
||||
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j/self.words_per_row)),
|
||||
layer="metal2",
|
||||
start=bl_out_offset,
|
||||
end=bl_out_offset_end)
|
||||
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j/self.words_per_row)),
|
||||
layer="metal2",
|
||||
start=br_out_offset,
|
||||
end=br_out_offset_end)
|
||||
|
||||
|
||||
# This via is on the right of the wire
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=bl_out_offset)
|
||||
|
||||
# This via is on the left of the wire
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=br_out_offset)
|
||||
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
|
||||
layer=self.bitline_layer,
|
||||
start=bl_offset_begin,
|
||||
end=bl_out_offset_begin)
|
||||
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)),
|
||||
layer=self.bitline_layer,
|
||||
start=br_offset_begin,
|
||||
end=br_out_offset_begin)
|
||||
|
||||
else:
|
||||
|
||||
self.add_path("metal2", [ bl_out_offset, bl_out_offset_end])
|
||||
self.add_path("metal2", [ br_out_offset, br_out_offset_end])
|
||||
|
||||
# This via is on the right of the wire
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=bl_out_offset)
|
||||
# This via is on the left of the wire
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=br_out_offset)
|
||||
self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
|
||||
self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin])
|
||||
|
||||
# This via is on the right of the wire
|
||||
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||
to_layer=self.sel_layer,
|
||||
offset=bl_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
|
||||
# This via is on the left of the wire
|
||||
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||
to_layer=self.sel_layer,
|
||||
offset=br_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
|
||||
def get_drain_cin(self):
|
||||
"""Get the relative capacitance of the drain of the NMOS pass TX"""
|
||||
from tech import parameter
|
||||
#Bitcell drain load being used to estimate mux NMOS drain load
|
||||
# Bitcell drain load being used to estimate mux NMOS drain load
|
||||
drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])
|
||||
return drain_load
|
||||
return drain_load
|
||||
|
|
|
|||
|
|
@ -83,14 +83,14 @@ class tri_gate_array(design.design):
|
|||
|
||||
in_pin = self.tri_inst[i].get_pin("in")
|
||||
self.add_layout_pin(text="in_{0}".format(index),
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=in_pin.ll(),
|
||||
width=in_pin.width(),
|
||||
height=in_pin.height())
|
||||
|
||||
out_pin = self.tri_inst[i].get_pin("out")
|
||||
self.add_layout_pin(text="out_{0}".format(index),
|
||||
layer="metal2",
|
||||
layer="m2",
|
||||
offset=out_pin.ll(),
|
||||
width=out_pin.width(),
|
||||
height=out_pin.height())
|
||||
|
|
@ -100,24 +100,24 @@ class tri_gate_array(design.design):
|
|||
for n in ["vdd", "gnd"]:
|
||||
for supply_pin in self.tri_inst[i].get_pins(n):
|
||||
pin_pos = supply_pin.center()
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=pin_pos)
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="metal3",
|
||||
layer="m3",
|
||||
offset=pin_pos)
|
||||
|
||||
|
||||
width = self.tri.width * self.columns - (self.words_per_row - 1) * self.tri.width
|
||||
en_pin = self.tri_inst[0].get_pin("en")
|
||||
self.add_layout_pin(text="en",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=en_pin.ll().scale(0, 1),
|
||||
width=width,
|
||||
height=drc("minwidth_metal1"))
|
||||
height=drc("minwidth_m1"))
|
||||
|
||||
enbar_pin = self.tri_inst[0].get_pin("en_bar")
|
||||
self.add_layout_pin(text="en_bar",
|
||||
layer="metal1",
|
||||
layer="m1",
|
||||
offset=enbar_pin.ll().scale(0, 1),
|
||||
width=width,
|
||||
height=drc("minwidth_metal1"))
|
||||
height=drc("minwidth_m1"))
|
||||
|
|
@ -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