mirror of https://github.com/VLSIDA/OpenRAM.git
End of 2018 release
This commit is contained in:
commit
05dc179e0b
15
.coveragerc
15
.coveragerc
|
|
@ -4,11 +4,26 @@ omit =
|
|||
*/.local/*
|
||||
# omit everything in /usr
|
||||
/usr/*
|
||||
# ignore the unit tests themselves
|
||||
*/tests/*
|
||||
# ignore the debug utilities
|
||||
debug.py
|
||||
[paths]
|
||||
source =
|
||||
../..
|
||||
/home/gitlab-runner/builds/2fd64746/0
|
||||
/home/gitlab-runner/builds/2fd64746/1
|
||||
/home/gitlab-runner/builds/2fd64746/2
|
||||
/home/gitlab-runner/builds/2fd64746/3
|
||||
/home/gitlab-runner/builds/2fd64746/4
|
||||
/home/gitlab-runner/builds/2fd64746/5
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
def __repr__
|
||||
except Exception
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
if 0:
|
||||
if __name__ == "__main__":
|
||||
if not OPTS.is_unit_test
|
||||
|
|
|
|||
|
|
@ -7,6 +7,26 @@ list at openram-dev-group@ucsc.edu. We are happy to give insights into
|
|||
the best way to implement a change to ensure your contribution will be
|
||||
accepted and help other OpenRAM users.
|
||||
|
||||
# Directory Structure
|
||||
|
||||
* compiler - openram compiler itself (pointed to by OPENRAM_HOME)
|
||||
* compiler/base - base data structure modules
|
||||
* compiler/pgates - parameterized cells (e.g. logic gates)
|
||||
* compiler/bitcells - various bitcell styles
|
||||
* compiler/modules - high-level modules (e.g. decoders, etc.)
|
||||
* compiler/verify - DRC and LVS verification wrappers
|
||||
* compiler/characterizer - timing characterization code
|
||||
* compiler/gdsMill - GDSII reader/writer
|
||||
* compiler/router - router for signals and power supplies
|
||||
* compiler/tests - unit tests
|
||||
* technology - openram technology directory (pointed to by OPENRAM_TECH)
|
||||
* technology/freepdk45 - example configuration library for [FreePDK45 technology node
|
||||
* technology/scn4m_subm - example configuration library [SCMOS] technology node
|
||||
* technology/scn3me_subm - unsupported configuration (not enough metal layers)
|
||||
* technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies
|
||||
* docs - LaTeX manual (outdated)
|
||||
* lib - IP library of pregenerated memories
|
||||
|
||||
# Code Style
|
||||
|
||||
Our code may not be the best and we acknowledge that. We welcome
|
||||
|
|
@ -20,7 +40,7 @@ other OpenRAM features. Please see the README.md file on how to run
|
|||
the unit tests. Unit tests should work in all technologies. We will run
|
||||
the tests on your contributions before they will be accepted.
|
||||
|
||||
# Internally Development
|
||||
# Internal Development
|
||||
|
||||
For internal development, follow all of the following steps EXCEPT
|
||||
do not fork your own copy. Instead, create a branch in our private repository
|
||||
|
|
@ -32,21 +52,21 @@ All unit tests should pass first.
|
|||
1. One time, create a GitHub account at http://github.com
|
||||
|
||||
2. Create a fork of the OpenRAM project on the github web page:
|
||||
https://github.com/mguthaus/OpenRAM
|
||||
https://github.com/vlsida/openram
|
||||
It is on the upper right and says "Fork": This will make your own
|
||||
OpenRAM repository on GitHub in your account.
|
||||
|
||||
3. Clone your repository (or use an existing cloned copy if you've
|
||||
already done this once):
|
||||
```
|
||||
git clone https://github.com/<youruser>/OpenRAM.git
|
||||
cd OpenRAM
|
||||
git clone https://github.com/<youruser>/oepnram.git
|
||||
cd openram
|
||||
```
|
||||
|
||||
4. Set up a new upstream that points to MY OpenRAM repository that you
|
||||
forked (only first time):
|
||||
```
|
||||
git remote add upstream https://github.com/mguthaus/OpenRAM.git
|
||||
git remote add upstream https://github.com/vlsida/openram.git
|
||||
```
|
||||
You now have two remotes for this project:
|
||||
* origin which points to your GitHub fork of the project. You can read
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
# Debugging
|
||||
|
||||
When OpenRAM runs, it puts files in a temporary directory that is
|
||||
shown in the banner at the top. Like:
|
||||
```
|
||||
/tmp/openram_mrg_18128_temp/
|
||||
```
|
||||
This is where simulations and DRC/LVS get run so there is no network
|
||||
traffic. The directory name is unique for each person and run of
|
||||
OpenRAM to not clobber any files and allow simultaneous runs. If it
|
||||
passes, the files are deleted. If it fails, you will see these files:
|
||||
+ temp.gds is the layout (.mag files too if using SCMOS)
|
||||
+ temp.sp is the netlist
|
||||
+ test1.drc.err is the std err output of the DRC command
|
||||
+ test1.drc.out is the standard output of the DRC command
|
||||
+ test1.drc.results is the DRC results file
|
||||
+ test1.lvs.err is the std err output of the LVS command
|
||||
+ test1.lvs.out is the standard output of the LVS command
|
||||
+ test1.lvs.results is the DRC results file
|
||||
|
||||
Depending on your DRC/LVS tools, there will also be:
|
||||
+ \_calibreDRC.rul\_ is the DRC rule file (Calibre)
|
||||
+ dc_runset is the command file (Calibre)
|
||||
+ extracted.sp (Calibre)
|
||||
+ run_lvs.sh is a Netgen script for LVS (Netgen)
|
||||
+ run_drc.sh is a Magic script for DRC (Magic)
|
||||
+ <topcell>.spice (Magic)
|
||||
|
||||
If DRC/LVS fails, the first thing is to check if it ran in the .out and
|
||||
.err file. This shows the standard output and error output from
|
||||
running DRC/LVS. If there is a setup problem it will be shown here.
|
||||
|
||||
If DRC/LVS runs, but doesn't pass, you then should look at the .results
|
||||
file. If the DRC fails, it will typically show you the command that was used
|
||||
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
|
||||
|
||||
Start the Calibre DESIGNrev viewer in the temp directory and load your GDS file:
|
||||
```
|
||||
calibredrv temp.gds
|
||||
```
|
||||
Select Verification->Start RVE and select the results database file in
|
||||
the new form (e.g., test1.drc.db). This will start the RVE (results
|
||||
viewer). Scroll through the check pane and find the DRC check with an
|
||||
error. Select it and it will open some numbers to the right. Double
|
||||
click on any of the errors in the result browser. These will be
|
||||
labelled as numbers "1 2 3 4" for example will be 4 DRC errors.
|
||||
|
||||
In the viewer ">" opens the layout down a level.
|
||||
|
||||
2. 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/
|
||||
|
||||
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:
|
||||
```
|
||||
export GLADE_USE_OPENGL=no
|
||||
```
|
||||
or in .cshrc/.tcshrc in csh/tcsh:
|
||||
```
|
||||
setenv GLADE_USE_OPENGAL no
|
||||
```
|
||||
To use this with the FreePDK45 or SCMOS layer views you should use the
|
||||
tech files. Then create a .glade.py file in your user directory with
|
||||
these commands to load the technology layers:
|
||||
```
|
||||
ui().importCds("default",
|
||||
"/Users/mrg/techfiles/freepdk45/display.drf",
|
||||
"/Users/mrg/techfiles/freepdk45/FreePDK45.tf", 1000, 1,
|
||||
"/Users/mrg/techfiles/freepdk45/layers.map")
|
||||
```
|
||||
Obviously, edit the paths to point to your directory. To switch
|
||||
between processes, you have to change the importCds command (or you
|
||||
can manually run the command each time you start glade).
|
||||
|
||||
To load the errors, you simply do Verify->Import Calibre Errors select
|
||||
the .results file from Calibre.
|
||||
|
||||
3. Magic
|
||||
|
||||
Magic is only supported in SCMOS. You will need to install the MOSIS SCMOS rules
|
||||
and Magic from: 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/
|
||||
|
||||
|
||||
# Example to output/input .gds layout files from/to Cadence
|
||||
|
||||
1. To create your component layouts, you should stream them to
|
||||
individual gds files using our provided layermap and flatten
|
||||
cells. For example,
|
||||
```
|
||||
strmout -layerMap layers.map -library sram -topCell $i -view layout -flattenVias -flattenPcells -strmFile ../gds_lib/$i.gds
|
||||
```
|
||||
2. To stream a layout back into Cadence, do this:
|
||||
```
|
||||
strmin -layerMap layers.map -attachTechFileOfLib NCSU\_TechLib\_FreePDK45 -library sram_4_32 -strmFile sram_4_32.gds
|
||||
```
|
||||
When you import a gds file, make sure to attach the correct tech lib
|
||||
or you will get incorrect layers in the resulting library.
|
||||
|
|
@ -16,7 +16,6 @@ Dev:
|
|||
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,
|
||||
|
|
@ -31,7 +30,6 @@ The OpenRAM compiler has very few dependencies:
|
|||
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
|
||||
+ Python 3.5 or higher
|
||||
+ Python numpy (pip3 install numpy to install)
|
||||
+ flask_table (pip3 install flask to install)
|
||||
|
||||
If you want to perform DRC and LVS, you will need either:
|
||||
+ Calibre (for [FreePDK45])
|
||||
|
|
@ -98,6 +96,12 @@ 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:
|
||||
|
|
|
|||
|
|
@ -1,93 +1,45 @@
|
|||
TECH = scn4m_subm
|
||||
CUR_DIR = $(shell pwd)
|
||||
TEST_DIR = ${CUR_DIR}/tests
|
||||
|
||||
MAKEFLAGS += -j 2
|
||||
MAKEFLAGS += -j 1
|
||||
|
||||
# Library test
|
||||
LIBRARY_TESTS = \
|
||||
01_library_drc_test.py \
|
||||
02_library_lvs_test.py
|
||||
LIBRARY_TESTS = $(shell find ${TEST_DIR} -name 0[1-2]*_test.py)
|
||||
|
||||
# Technology and DRC tests (along with ptx)
|
||||
TECH_TESTS = \
|
||||
03_contact_test.py \
|
||||
03_ptx_1finger_pmos_test.py \
|
||||
03_ptx_4finger_nmos_test.py \
|
||||
03_path_test.py \
|
||||
03_ptx_3finger_nmos_test.py \
|
||||
03_ptx_4finger_pmos_test.py \
|
||||
03_ptx_1finger_nmos_test.py \
|
||||
03_ptx_3finger_pmos_test.py \
|
||||
03_wire_test.py
|
||||
TECH_TESTS = $(shell find ${TEST_DIR} -name 03*_test.py)
|
||||
|
||||
# Parameterized cells
|
||||
PCELLS_TESTS = \
|
||||
04_pinv_1x_test.py \
|
||||
04_pinv_1x_beta_test.py \
|
||||
04_pinv_2x_test.py \
|
||||
04_pinv_10x_test.py \
|
||||
04_pnand2_test.py \
|
||||
04_pnor2_test.py \
|
||||
04_pnand3_test.py\
|
||||
04_wordline_driver_test.py \
|
||||
04_precharge_test.py
|
||||
CELL_TESTS = $(shell find ${TEST_DIR} -name 04*_test.py)
|
||||
|
||||
# Dynamically generated modules and arrays
|
||||
MODULE_TESTS = \
|
||||
05_bitcell_array_test.py \
|
||||
06_hierarchical_decoder_test.py \
|
||||
06_hierarchical_predecode2x4_test.py \
|
||||
06_hierarchical_predecode3x8_test.py \
|
||||
07_single_level_column_mux_array_test.py \
|
||||
08_precharge_array_test.py \
|
||||
09_sense_amp_array_test.py \
|
||||
10_write_driver_array_test.py \
|
||||
11_ms_flop_array_test.py \
|
||||
12_tri_gate_array_test.py \
|
||||
13_delay_chain_test.py \
|
||||
14_replica_bitline_test.py \
|
||||
16_control_logic_test.py
|
||||
MODULE_TESTS = $(shell find ${TEST_DIR} -name 0[5-9]*_test.py)\
|
||||
$(shell find ${TEST_DIR} -name 1*_test.py)
|
||||
|
||||
# Top-level SRAM configurations
|
||||
TOP_TESTS = \
|
||||
19_multi_bank_test.py \
|
||||
19_single_bank_test.py \
|
||||
20_sram_1bank_test.py \
|
||||
20_sram_2bank_test.py \
|
||||
20_sram_4bank_test.py
|
||||
TOP_TESTS = $(shell find ${TEST_DIR} -name 20*_test.py)
|
||||
|
||||
# All simulation tests.
|
||||
CHAR_TESTS = \
|
||||
21_hspice_delay_test.py \
|
||||
21_ngspice_delay_test.py \
|
||||
21_ngspice_setuphold_test.py \
|
||||
21_hspice_setuphold_test.py \
|
||||
22_sram_func_test.py \
|
||||
22_pex_func_test_with_pinv.py \
|
||||
23_lib_sram_prune_test.py \
|
||||
23_lib_sram_test.py
|
||||
CHAR_TESTS = $(shell find ${TEST_DIR} -name 2[1-2]*_test.py)
|
||||
|
||||
# Keep the model lib test here since it is fast
|
||||
# and doesn't need simulation.
|
||||
USAGE_TESTS = \
|
||||
23_lib_sram_model_test.py \
|
||||
24_lef_sram_test.py \
|
||||
25_verilog_sram_test.py
|
||||
USAGE_TESTS = $(shell find ${TEST_DIR} -name 2[3-9]*_test.py)\
|
||||
$(shell find ${TEST_DIR} -name 30*_test.py)
|
||||
|
||||
ALL_FILES = \
|
||||
ALL_TESTS = \
|
||||
${LIBRARY_TESTS} \
|
||||
${TECH_TESTS} \
|
||||
${PCELLS_TESTS} \
|
||||
${MODULES_TESTS} \
|
||||
${CELL_TESTS} \
|
||||
${MODULE_TESTS} \
|
||||
${TOP_TESTS} \
|
||||
${CHAR_TESTS} \
|
||||
${USAGE_TESTS}
|
||||
|
||||
default all:
|
||||
.PHONY: ${ALL_TESTS}
|
||||
|
||||
$(ALL_FILES):
|
||||
python ${TEST_DIR}/$@ -t freepdk45
|
||||
python ${TEST_DIR}/$@ -t scn3me_subm
|
||||
all: ${ALL_TESTS}
|
||||
|
||||
# Library tests
|
||||
lib: ${LIBRARY_TESTS}
|
||||
|
|
@ -96,10 +48,10 @@ lib: ${LIBRARY_TESTS}
|
|||
tech: ${TECH_TESTS}
|
||||
|
||||
# Dynamically generated cells
|
||||
pcells: ${PCELLS_TESTS}
|
||||
cell: ${CELL_TESTS}
|
||||
|
||||
# Dynamically generated modules
|
||||
modules: ${MODULES_TESTS}
|
||||
module: ${MODULE_TESTS}
|
||||
|
||||
# Top level SRAM tests
|
||||
top: ${TOP_TESTS}
|
||||
|
|
@ -110,6 +62,9 @@ char: ${CHAR_TESTS}
|
|||
# Usage and file generation
|
||||
usage: ${USAGE_TESTS}
|
||||
|
||||
$(ALL_TESTS):
|
||||
python3 $@ -t ${TECH}
|
||||
|
||||
clean:
|
||||
find . -name \*.pyc -exec rm {} \;
|
||||
find . -name \*~ -exec rm {} \;
|
||||
|
|
|
|||
|
|
@ -73,21 +73,21 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
self.second_layer_name = second_layer
|
||||
|
||||
def setup_layout_constants(self):
|
||||
self.contact_width = drc["minwidth_{0}". format(self.via_layer_name)]
|
||||
contact_to_contact = drc["{0}_to_{0}".format(self.via_layer_name)]
|
||||
self.contact_width = drc("minwidth_{0}". format(self.via_layer_name))
|
||||
contact_to_contact = drc("{0}_to_{0}".format(self.via_layer_name))
|
||||
self.contact_pitch = self.contact_width + contact_to_contact
|
||||
self.contact_array_width = self.contact_width + (self.dimensions[0] - 1) * self.contact_pitch
|
||||
self.contact_array_height = self.contact_width + (self.dimensions[1] - 1) * self.contact_pitch
|
||||
|
||||
# DRC rules
|
||||
first_layer_minwidth = drc["minwidth_{0}".format(self.first_layer_name)]
|
||||
first_layer_minarea = drc["minarea_{0}".format(self.first_layer_name)]
|
||||
first_layer_enclosure = drc["{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name)]
|
||||
first_layer_extend = drc["{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)]
|
||||
second_layer_minwidth = drc["minwidth_{0}".format(self.second_layer_name)]
|
||||
second_layer_minarea = drc["minarea_{0}".format(self.second_layer_name)]
|
||||
second_layer_enclosure = drc["{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)]
|
||||
second_layer_extend = drc["{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)]
|
||||
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||
first_layer_minarea = drc("minarea_{0}".format(self.first_layer_name))
|
||||
first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||
second_layer_minarea = drc("minarea_{0}".format(self.second_layer_name))
|
||||
second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
|
||||
self.first_layer_horizontal_enclosure = max((first_layer_minwidth - self.contact_array_width) / 2,
|
||||
first_layer_enclosure)
|
||||
|
|
@ -145,16 +145,16 @@ class contact(hierarchy_design.hierarchy_design):
|
|||
height=self.second_layer_height)
|
||||
|
||||
def create_implant_well_enclosures(self):
|
||||
implant_position = self.first_layer_position - [drc["implant_enclosure_active"]]*2
|
||||
implant_width = self.first_layer_width + 2*drc["implant_enclosure_active"]
|
||||
implant_height = self.first_layer_height + 2*drc["implant_enclosure_active"]
|
||||
implant_position = self.first_layer_position - [drc("implant_enclosure_active")]*2
|
||||
implant_width = self.first_layer_width + 2*drc("implant_enclosure_active")
|
||||
implant_height = self.first_layer_height + 2*drc("implant_enclosure_active")
|
||||
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||
offset=implant_position,
|
||||
width=implant_width,
|
||||
height=implant_height)
|
||||
well_position = self.first_layer_position - [drc["well_enclosure_active"]]*2
|
||||
well_width = self.first_layer_width + 2*drc["well_enclosure_active"]
|
||||
well_height = self.first_layer_height + 2*drc["well_enclosure_active"]
|
||||
well_position = self.first_layer_position - [drc("well_enclosure_active")]*2
|
||||
well_width = self.first_layer_width + 2*drc("well_enclosure_active")
|
||||
well_height = self.first_layer_height + 2*drc("well_enclosure_active")
|
||||
self.add_rect(layer="{}well".format(self.well_type),
|
||||
offset=well_position,
|
||||
width=well_width,
|
||||
|
|
@ -172,5 +172,5 @@ active = contact(layer_stack=("active", "contact", "poly"))
|
|||
poly = contact(layer_stack=("poly", "contact", "metal1"))
|
||||
m1m2 = contact(layer_stack=("metal1", "via1", "metal2"))
|
||||
m2m3 = contact(layer_stack=("metal2", "via2", "metal3"))
|
||||
#m3m4 = contact(layer_stack=("metal3", "via3", "metal4"))
|
||||
m3m4 = contact(layer_stack=("metal3", "via3", "metal4"))
|
||||
|
||||
|
|
|
|||
|
|
@ -18,34 +18,76 @@ class design(hierarchy_design):
|
|||
hierarchy_design.__init__(self,name)
|
||||
|
||||
self.setup_drc_constants()
|
||||
self.setup_multiport_constants()
|
||||
|
||||
self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space)
|
||||
self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space)
|
||||
# SCMOS doesn't have m4...
|
||||
#self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space)
|
||||
self.m3_pitch = self.m2_pitch
|
||||
self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space)
|
||||
|
||||
def setup_drc_constants(self):
|
||||
""" These are some DRC constants used in many places in the compiler."""
|
||||
from tech import drc
|
||||
self.well_width = drc["minwidth_well"]
|
||||
self.poly_width = drc["minwidth_poly"]
|
||||
self.poly_space = drc["poly_to_poly"]
|
||||
self.m1_width = drc["minwidth_metal1"]
|
||||
self.m1_space = drc["metal1_to_metal1"]
|
||||
self.m2_width = drc["minwidth_metal2"]
|
||||
self.m2_space = drc["metal2_to_metal2"]
|
||||
self.m3_width = drc["minwidth_metal3"]
|
||||
self.m3_space = drc["metal3_to_metal3"]
|
||||
self.active_width = drc["minwidth_active"]
|
||||
self.contact_width = drc["minwidth_contact"]
|
||||
self.well_width = drc("minwidth_well")
|
||||
self.poly_width = drc("minwidth_poly")
|
||||
self.poly_space = drc("poly_to_poly")
|
||||
self.m1_width = drc("minwidth_metal1")
|
||||
self.m1_space = drc("metal1_to_metal1")
|
||||
self.m2_width = drc("minwidth_metal2")
|
||||
self.m2_space = drc("metal2_to_metal2")
|
||||
self.m3_width = drc("minwidth_metal3")
|
||||
self.m3_space = drc("metal3_to_metal3")
|
||||
self.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")
|
||||
|
||||
self.poly_to_active = drc["poly_to_active"]
|
||||
self.poly_extend_active = drc["poly_extend_active"]
|
||||
self.contact_to_gate = drc["contact_to_gate"]
|
||||
self.well_enclose_active = drc["well_enclosure_active"]
|
||||
self.implant_enclose_active = drc["implant_enclosure_active"]
|
||||
self.implant_space = drc["implant_to_implant"]
|
||||
self.poly_to_active = drc("poly_to_active")
|
||||
self.poly_extend_active = drc("poly_extend_active")
|
||||
self.poly_to_polycontact = drc("poly_to_polycontact")
|
||||
self.contact_to_gate = drc("contact_to_gate")
|
||||
self.well_enclose_active = drc("well_enclosure_active")
|
||||
self.implant_enclose_active = drc("implant_enclosure_active")
|
||||
self.implant_space = drc("implant_to_implant")
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
These are contants and lists that aid multiport design.
|
||||
Ports are always in the order RW, W, R.
|
||||
Port indices start from 0 and increment.
|
||||
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||
|
||||
"""
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
# These are the read/write port indices.
|
||||
self.readwrite_ports = []
|
||||
# These are the read/write and write-only port indices
|
||||
self.write_ports = []
|
||||
# These are the write-only port indices.
|
||||
self.writeonly_ports = []
|
||||
# These are teh read/write and read-only port indice
|
||||
self.read_ports = []
|
||||
# These are the read-only port indices.
|
||||
self.readonly_ports = []
|
||||
# These are all the ports
|
||||
self.all_ports = list(range(total_ports))
|
||||
|
||||
port_number = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.readwrite_ports.append(port_number)
|
||||
self.write_ports.append(port_number)
|
||||
self.read_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.write_ports.append(port_number)
|
||||
self.writeonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_r_ports):
|
||||
self.read_ports.append(port_number)
|
||||
self.readonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
|
||||
def analytical_power(self, proc, vdd, temp, load):
|
||||
""" Get total power of a module """
|
||||
|
|
@ -53,3 +95,14 @@ class design(hierarchy_design):
|
|||
for inst in self.insts:
|
||||
total_module_power += inst.mod.analytical_power(proc, vdd, temp, load)
|
||||
return total_module_power
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
pins = ",".join(self.pins)
|
||||
insts = [" {}".format(x) for x in self.insts]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
s = "********** design {0} **********\n".format(self.name)
|
||||
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
|
||||
s += "\n objs ({0})=\n{1}".format(len(self.objs), "\n".join(objs))
|
||||
s += "\n insts ({0})=\n{1}\n".format(len(self.insts), "\n".join(insts))
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from vector import vector
|
|||
import tech
|
||||
import math
|
||||
from globals import OPTS
|
||||
from utils import round_to_grid
|
||||
|
||||
class geometry:
|
||||
"""
|
||||
|
|
@ -46,8 +47,8 @@ class geometry:
|
|||
def normalize(self):
|
||||
""" Re-find the LL and UR points after a transform """
|
||||
(first,second)=self.boundary
|
||||
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
|
||||
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
|
||||
ll = vector(min(first[0],second[0]),min(first[1],second[1])).snap_to_grid()
|
||||
ur = vector(max(first[0],second[0]),max(first[1],second[1])).snap_to_grid()
|
||||
self.boundary=[ll,ur]
|
||||
|
||||
def update_boundary(self):
|
||||
|
|
@ -142,8 +143,16 @@ class instance(geometry):
|
|||
self.rotate = rotate
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
self.mirror = mirror
|
||||
self.width = mod.width
|
||||
self.height = mod.height
|
||||
if OPTS.netlist_only:
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
else:
|
||||
if mirror in ["R90","R270"] or rotate in [90,270]:
|
||||
self.width = round_to_grid(mod.height)
|
||||
self.height = round_to_grid(mod.width)
|
||||
else:
|
||||
self.width = round_to_grid(mod.width)
|
||||
self.height = round_to_grid(mod.height)
|
||||
self.compute_boundary(offset,mirror,rotate)
|
||||
|
||||
debug.info(4, "creating instance: " + self.name)
|
||||
|
|
@ -191,18 +200,19 @@ class instance(geometry):
|
|||
self.mod.gds_write_file(self.gds)
|
||||
# now write an instance of my module/structure
|
||||
new_layout.addInstance(self.gds,
|
||||
offsetInMicrons=self.offset,
|
||||
mirror=self.mirror,
|
||||
rotate=self.rotate)
|
||||
|
||||
self.mod.name,
|
||||
offsetInMicrons=self.offset,
|
||||
mirror=self.mirror,
|
||||
rotate=self.rotate)
|
||||
|
||||
def place(self, offset, mirror="R0", rotate=0):
|
||||
""" This updates the placement of an instance. """
|
||||
debug.info(3, "placing instance {}".format(self.name))
|
||||
# Update the placement of an already added instance
|
||||
self.offset = vector(offset)
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
self.mirror = mirror
|
||||
self.rotate = rotate
|
||||
self.update_boundary()
|
||||
debug.info(3, "placing instance {}".format(self))
|
||||
|
||||
|
||||
def get_pin(self,name,index=-1):
|
||||
|
|
@ -238,7 +248,7 @@ class instance(geometry):
|
|||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "inst: " + self.name + " mod=" + self.mod.name
|
||||
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
|
|
@ -260,13 +270,13 @@ class path(geometry):
|
|||
# supported right now. It might not work in gdsMill.
|
||||
assert(0)
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Writes the path to GDS"""
|
||||
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
|
||||
newLayout.addPath(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
coordinates=self.coordinates,
|
||||
width=self.path_width)
|
||||
new_layout.addPath(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
coordinates=self.coordinates,
|
||||
width=self.path_width)
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Fail since we don't support paths yet. """
|
||||
|
|
@ -301,15 +311,15 @@ class label(geometry):
|
|||
|
||||
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Writes the text label to GDS"""
|
||||
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
|
||||
newLayout.addText(text=self.text,
|
||||
layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
offsetInMicrons=self.offset,
|
||||
magnification=self.zoom,
|
||||
rotate=None)
|
||||
new_layout.addText(text=self.text,
|
||||
layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
offsetInMicrons=self.offset,
|
||||
magnification=self.zoom,
|
||||
rotate=None)
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Returns an empty list since text cannot be blockages. """
|
||||
|
|
@ -321,7 +331,7 @@ class label(geometry):
|
|||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + self.layerNumber + " )"
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
|
||||
|
||||
class rectangle(geometry):
|
||||
"""Represents a rectangular shape"""
|
||||
|
|
@ -333,8 +343,8 @@ class rectangle(geometry):
|
|||
self.layerNumber = layerNumber
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
self.size = vector(width, height).snap_to_grid()
|
||||
self.width = self.size.x
|
||||
self.height = self.size.y
|
||||
self.width = round_to_grid(self.size.x)
|
||||
self.height = round_to_grid(self.size.y)
|
||||
self.compute_boundary(offset,"",0)
|
||||
|
||||
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
||||
|
|
@ -348,16 +358,16 @@ class rectangle(geometry):
|
|||
else:
|
||||
return []
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Writes the rectangular shape to GDS"""
|
||||
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
|
||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||
newLayout.addBox(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
offsetInMicrons=self.offset,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
center=False)
|
||||
new_layout.addBox(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
offsetInMicrons=self.offset,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
center=False)
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import debug
|
|||
import os
|
||||
from globals import OPTS
|
||||
|
||||
total_drc_errors = 0
|
||||
total_lvs_errors = 0
|
||||
|
||||
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
"""
|
||||
|
|
@ -13,7 +15,6 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
Class consisting of a set of modules and instances of these modules
|
||||
"""
|
||||
name_map = []
|
||||
|
||||
|
||||
def __init__(self, name):
|
||||
try:
|
||||
|
|
@ -28,26 +29,30 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
self.name = name
|
||||
hierarchy_layout.layout.__init__(self, name)
|
||||
hierarchy_spice.spice.__init__(self, name)
|
||||
|
||||
|
||||
|
||||
# Check if the name already exists, if so, give an error
|
||||
# because each reference must be a unique name.
|
||||
# These modules ensure unique names or have no changes if they
|
||||
# aren't unique
|
||||
ok_list = ['ms_flop',
|
||||
'dff',
|
||||
'dff_buf',
|
||||
'bitcell',
|
||||
'contact',
|
||||
ok_list = ['contact',
|
||||
'ptx',
|
||||
'pbitcell',
|
||||
'replica_pbitcell',
|
||||
'sram',
|
||||
'hierarchical_predecode2x4',
|
||||
'hierarchical_predecode3x8']
|
||||
if name not in hierarchy_design.name_map:
|
||||
|
||||
# Library cells don't change
|
||||
if self.is_library_cell:
|
||||
return
|
||||
# Name is unique so far
|
||||
elif name not in hierarchy_design.name_map:
|
||||
hierarchy_design.name_map.append(name)
|
||||
else:
|
||||
# Name is in our list of exceptions (they don't change)
|
||||
for ok_names in ok_list:
|
||||
if ok_names in self.__class__.__name__:
|
||||
if ok_names == self.__class__.__name__:
|
||||
break
|
||||
else:
|
||||
debug.error("Duplicate layout reference name {0} of class {1}. GDS2 requires names be unique.".format(name,self.__class__),-1)
|
||||
|
|
@ -69,36 +74,55 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
"""Checks both DRC and LVS for a module"""
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
if not OPTS.is_unit_test and OPTS.check_lvsdrc:
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
|
||||
global total_drc_errors
|
||||
global total_lvs_errors
|
||||
tempspice = OPTS.openram_temp + "/temp.sp"
|
||||
tempgds = OPTS.openram_temp + "/temp.gds"
|
||||
self.sp_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
debug.check(verify.run_drc(self.name, tempgds) == 0,"DRC failed for {0}".format(self.name))
|
||||
debug.check(verify.run_lvs(self.name, tempgds, tempspice, final_verification) == 0,"LVS failed for {0}".format(self.name))
|
||||
|
||||
num_drc_errors = verify.run_drc(self.name, tempgds, final_verification)
|
||||
num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, 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)
|
||||
|
||||
def DRC(self):
|
||||
def DRC(self, final_verification=False):
|
||||
"""Checks DRC for a module"""
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
if not OPTS.is_unit_test and OPTS.check_lvsdrc:
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_drc_errors
|
||||
tempgds = OPTS.openram_temp + "/temp.gds"
|
||||
self.gds_write(tempgds)
|
||||
debug.check(verify.run_drc(self.name, tempgds) == 0,"DRC failed for {0}".format(self.name))
|
||||
num_errors = verify.run_drc(self.name, tempgds, 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))
|
||||
|
||||
os.remove(tempgds)
|
||||
|
||||
def LVS(self, final_verification=False):
|
||||
"""Checks LVS for a module"""
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
if not OPTS.is_unit_test and OPTS.check_lvsdrc:
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_lvs_errors
|
||||
tempspice = OPTS.openram_temp + "/temp.sp"
|
||||
tempgds = OPTS.openram_temp + "/temp.gds"
|
||||
self.sp_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
debug.check(verify.run_lvs(self.name, tempgds, tempspice, final_verification) == 0,"LVS failed for {0}".format(self.name))
|
||||
num_errors = verify.run_lvs(self.name, tempgds, tempspice, 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from vector import vector
|
|||
from pin_layout import pin_layout
|
||||
import lef
|
||||
|
||||
class layout(lef.lef):
|
||||
class layout():
|
||||
"""
|
||||
Class consisting of a set of objs and instances for a module
|
||||
This provides a set of useful generic types for hierarchy
|
||||
|
|
@ -21,14 +21,13 @@ class layout(lef.lef):
|
|||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
lef.lef.__init__(self, ["metal1", "metal2", "metal3"])
|
||||
self.name = name
|
||||
self.width = None
|
||||
self.height = None
|
||||
self.insts = [] # Holds module/cell layout instances
|
||||
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
||||
self.pin_map = {} # Holds name->pin_layout map for all pins
|
||||
self.visited = False # Flag for traversing the hierarchy
|
||||
self.visited = [] # List of modules we have already visited
|
||||
self.is_library_cell = False # Flag for library cells
|
||||
self.gds_read()
|
||||
|
||||
|
|
@ -134,11 +133,13 @@ class layout(lef.lef):
|
|||
return inst
|
||||
return None
|
||||
|
||||
def add_rect(self, layer, offset, width=0, height=0):
|
||||
"""Adds a rectangle on a given layer,offset with width and height"""
|
||||
if width==0:
|
||||
def add_rect(self, layer, offset, width=None, height=None):
|
||||
"""
|
||||
Adds a rectangle on a given layer,offset with width and height
|
||||
"""
|
||||
if not width:
|
||||
width=drc["minwidth_{}".format(layer)]
|
||||
if height==0:
|
||||
if not height:
|
||||
height=drc["minwidth_{}".format(layer)]
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
layer_num = techlayer[layer]
|
||||
|
|
@ -147,11 +148,13 @@ class layout(lef.lef):
|
|||
return self.objs[-1]
|
||||
return None
|
||||
|
||||
def add_rect_center(self, layer, offset, width=0, height=0):
|
||||
"""Adds a rectangle on a given layer at the center point with width and height"""
|
||||
if width==0:
|
||||
def add_rect_center(self, layer, offset, width=None, height=None):
|
||||
"""
|
||||
Adds a rectangle on a given layer at the center point with width and height
|
||||
"""
|
||||
if not width:
|
||||
width=drc["minwidth_{}".format(layer)]
|
||||
if height==0:
|
||||
if not height:
|
||||
height=drc["minwidth_{}".format(layer)]
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
layer_num = techlayer[layer]
|
||||
|
|
@ -163,7 +166,9 @@ class layout(lef.lef):
|
|||
|
||||
|
||||
def add_segment_center(self, layer, start, end):
|
||||
""" Add a min-width rectanglular segment using center line on the start to end point """
|
||||
"""
|
||||
Add a min-width rectanglular segment using center line on the start to end point
|
||||
"""
|
||||
minwidth_layer = drc["minwidth_{}".format(layer)]
|
||||
if start.x!=end.x and start.y!=end.y:
|
||||
debug.error("Nonrectilinear center rect!",-1)
|
||||
|
|
@ -177,7 +182,9 @@ class layout(lef.lef):
|
|||
|
||||
|
||||
def get_pin(self, text):
|
||||
""" Return the pin or list of pins """
|
||||
"""
|
||||
Return the pin or list of pins
|
||||
"""
|
||||
try:
|
||||
if len(self.pin_map[text])>1:
|
||||
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
|
||||
|
|
@ -192,8 +199,13 @@ class layout(lef.lef):
|
|||
|
||||
|
||||
def get_pins(self, text):
|
||||
""" Return a pin list (instead of a single pin) """
|
||||
return self.pin_map[text]
|
||||
"""
|
||||
Return a pin list (instead of a single pin)
|
||||
"""
|
||||
if text in self.pin_map.keys():
|
||||
return self.pin_map[text]
|
||||
else:
|
||||
return []
|
||||
|
||||
def copy_layout_pin(self, instance, pin_name, new_name=""):
|
||||
"""
|
||||
|
|
@ -207,7 +219,9 @@ class layout(lef.lef):
|
|||
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
|
||||
|
||||
def add_layout_pin_segment_center(self, text, layer, start, end):
|
||||
""" Creates a path like pin with center-line convention """
|
||||
"""
|
||||
Creates a path like pin with center-line convention
|
||||
"""
|
||||
|
||||
debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.")
|
||||
|
||||
|
|
@ -232,9 +246,9 @@ class layout(lef.lef):
|
|||
|
||||
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
|
||||
""" Creates a path like pin with center-line convention """
|
||||
if width==None:
|
||||
if not width:
|
||||
width=drc["minwidth_{0}".format(layer)]
|
||||
if height==None:
|
||||
if not height:
|
||||
height=drc["minwidth_{0}".format(layer)]
|
||||
|
||||
ll_offset = offset - vector(0.5*width,0.5*height)
|
||||
|
|
@ -243,14 +257,18 @@ class layout(lef.lef):
|
|||
|
||||
|
||||
def remove_layout_pin(self, text):
|
||||
"""Delete a labeled pin (or all pins of the same name)"""
|
||||
"""
|
||||
Delete a labeled pin (or all pins of the same name)
|
||||
"""
|
||||
self.pin_map[text]=[]
|
||||
|
||||
def add_layout_pin(self, text, layer, offset, width=None, height=None):
|
||||
"""Create a labeled pin """
|
||||
if width==None:
|
||||
"""
|
||||
Create a labeled pin
|
||||
"""
|
||||
if not width:
|
||||
width=drc["minwidth_{0}".format(layer)]
|
||||
if height==None:
|
||||
if not height:
|
||||
height=drc["minwidth_{0}".format(layer)]
|
||||
|
||||
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
|
||||
|
|
@ -270,13 +288,14 @@ class layout(lef.lef):
|
|||
return new_pin
|
||||
|
||||
def add_label_pin(self, text, layer, offset, width=None, height=None):
|
||||
"""Create a labeled pin WITHOUT the pin data structure. This is not an
|
||||
"""
|
||||
Create a labeled pin WITHOUT the pin data structure. This is not an
|
||||
actual pin but a named net so that we can add a correspondence point
|
||||
in LVS.
|
||||
"""
|
||||
if width==None:
|
||||
if not width:
|
||||
width=drc["minwidth_{0}".format(layer)]
|
||||
if height==None:
|
||||
if not height:
|
||||
height=drc["minwidth_{0}".format(layer)]
|
||||
self.add_rect(layer=layer,
|
||||
offset=offset,
|
||||
|
|
@ -313,7 +332,7 @@ class layout(lef.lef):
|
|||
position_list=coordinates,
|
||||
width=width)
|
||||
|
||||
def add_route(self, layers, coordinates):
|
||||
def add_route(self, layers, coordinates, layer_widths):
|
||||
"""Connects a routing path on given layer,coordinates,width. The
|
||||
layers are the (horizontal, via, vertical). add_wire assumes
|
||||
preferred direction routing whereas this includes layers in
|
||||
|
|
@ -324,7 +343,8 @@ class layout(lef.lef):
|
|||
# add an instance of our path that breaks down into rectangles and contacts
|
||||
route.route(obj=self,
|
||||
layer_stack=layers,
|
||||
path=coordinates)
|
||||
path=coordinates,
|
||||
layer_widths=layer_widths)
|
||||
|
||||
|
||||
def add_wire(self, layers, coordinates):
|
||||
|
|
@ -426,65 +446,76 @@ class layout(lef.lef):
|
|||
def gds_read(self):
|
||||
"""Reads a GDSII file in the library and checks if it exists
|
||||
Otherwise, start a new layout for dynamic generation."""
|
||||
|
||||
# This must be done for netlist only mode too
|
||||
if os.path.isfile(self.gds_file):
|
||||
self.is_library_cell=True
|
||||
|
||||
if OPTS.netlist_only:
|
||||
self.gds = None
|
||||
return
|
||||
|
||||
# open the gds file if it exists or else create a blank layout
|
||||
if os.path.isfile(self.gds_file):
|
||||
debug.info(3, "opening %s" % self.gds_file)
|
||||
self.is_library_cell=True
|
||||
debug.info(3, "opening {}".format(self.gds_file))
|
||||
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
|
||||
reader = gdsMill.Gds2reader(self.gds)
|
||||
reader.loadFromFile(self.gds_file)
|
||||
else:
|
||||
debug.info(4, "creating structure %s" % self.name)
|
||||
debug.info(3, "Creating layout structure {}".format(self.name))
|
||||
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
|
||||
|
||||
def print_gds(self, gds_file=None):
|
||||
"""Print the gds file (not the vlsi class) to the terminal """
|
||||
if gds_file == None:
|
||||
gds_file = self.gds_file
|
||||
debug.info(4, "Printing %s" % gds_file)
|
||||
debug.info(4, "Printing {}".format(gds_file))
|
||||
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
|
||||
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
|
||||
reader.loadFromFile(gds_file)
|
||||
|
||||
def clear_visited(self):
|
||||
""" Recursively clear the visited flag """
|
||||
if not self.visited:
|
||||
for i in self.insts:
|
||||
i.mod.clear_visited()
|
||||
self.visited = False
|
||||
self.visited = []
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
def gds_write_file(self, gds_layout):
|
||||
"""Recursive GDS write function"""
|
||||
# Visited means that we already prepared self.gds for this subtree
|
||||
if self.visited:
|
||||
if self.name in self.visited:
|
||||
return
|
||||
for i in self.insts:
|
||||
i.gds_write_file(newLayout)
|
||||
i.gds_write_file(gds_layout)
|
||||
for i in self.objs:
|
||||
i.gds_write_file(newLayout)
|
||||
i.gds_write_file(gds_layout)
|
||||
for pin_name in self.pin_map.keys():
|
||||
for pin in self.pin_map[pin_name]:
|
||||
pin.gds_write_file(newLayout)
|
||||
self.visited = True
|
||||
pin.gds_write_file(gds_layout)
|
||||
self.visited.append(self.name)
|
||||
|
||||
def gds_write(self, gds_name):
|
||||
"""Write the entire gds of the object to the file."""
|
||||
debug.info(3, "Writing to {0}".format(gds_name))
|
||||
debug.info(3, "Writing to {}".format(gds_name))
|
||||
|
||||
# If we already wrote a GDS, we need to reset and traverse it again in
|
||||
# case we made changes.
|
||||
if not self.is_library_cell and self.visited:
|
||||
debug.info(3, "Creating layout structure {}".format(self.name))
|
||||
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
|
||||
|
||||
writer = gdsMill.Gds2writer(self.gds)
|
||||
# MRG: 3/2/18 We don't want to clear the visited flag since
|
||||
# this would result in duplicates of all instances being placed in self.gds
|
||||
# which may have been previously processed!
|
||||
#self.clear_visited()
|
||||
# MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
|
||||
self.clear_visited()
|
||||
|
||||
# recursively create all the remaining objects
|
||||
self.gds_write_file(self.gds)
|
||||
|
||||
# populates the xyTree data structure for gds
|
||||
# self.gds.prepareForWrite()
|
||||
writer.writeToFile(gds_name)
|
||||
debug.info(3, "Done writing to {}".format(gds_name))
|
||||
|
||||
def get_boundary(self):
|
||||
""" Return the lower-left and upper-right coordinates of boundary """
|
||||
|
|
@ -641,11 +672,13 @@ class layout(lef.lef):
|
|||
offset=bus_pos,
|
||||
rotate=90)
|
||||
|
||||
def add_horizontal_trunk_route(self, pins, trunk_offset,
|
||||
def add_horizontal_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
pitch=None):
|
||||
"""
|
||||
Create a trunk route for all pins with the the trunk located at the given y offset.
|
||||
Create a trunk route for all pins with the trunk located at the given y offset.
|
||||
"""
|
||||
if not pitch:
|
||||
pitch = self.m1_pitch
|
||||
|
|
@ -672,15 +705,18 @@ class layout(lef.lef):
|
|||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Bend to the center of the trunk so it adds a via automatically
|
||||
mid = vector(pin.center().x, trunk_offset.y)
|
||||
self.add_wire(layer_stack, [pin.center(), mid, trunk_mid])
|
||||
self.add_path(layer_stack[2], [pin.center(), mid])
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=mid)
|
||||
|
||||
def add_vertical_trunk_route(self, pins, trunk_offset,
|
||||
def add_vertical_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
pitch=None):
|
||||
"""
|
||||
Create a trunk route for all pins with the the trunk located at the given x offset.
|
||||
Create a trunk route for all pins with the trunk located at the given x offset.
|
||||
"""
|
||||
if not pitch:
|
||||
pitch = self.m2_pitch
|
||||
|
|
@ -708,54 +744,66 @@ class layout(lef.lef):
|
|||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Bend to the center of the trunk so it adds a via automatically
|
||||
mid = vector(trunk_offset.x, pin.center().y)
|
||||
self.add_wire(layer_stack, [pin.center(), mid, trunk_mid])
|
||||
self.add_path(layer_stack[0], [pin.center(), mid])
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=mid,
|
||||
rotate=90)
|
||||
|
||||
|
||||
def create_channel_route(self, netlist, pins, offset,
|
||||
layer_stack=("metal1", "via1", "metal2"), pitch=None,
|
||||
def create_channel_route(self, netlist,
|
||||
offset,
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
pitch=None,
|
||||
vertical=False):
|
||||
"""
|
||||
The net list is a list of the nets. Each net is a list of pin
|
||||
names to be connected. Pins is a dictionary of the pin names
|
||||
to the pin structures. Offset is the lower-left of where the
|
||||
The net list is a list of the nets. Each net is a list of pins
|
||||
to be connected. Offset is the lower-left of where the
|
||||
routing channel will start. This does NOT try to minimize the
|
||||
number of tracks -- instead, it picks an order to avoid the
|
||||
vertical conflicts between pins.
|
||||
|
||||
"""
|
||||
def remove_net_from_graph(pin, g):
|
||||
# Remove the pin from the keys
|
||||
"""
|
||||
Remove the pin from the graph and all conflicts
|
||||
"""
|
||||
g.pop(pin,None)
|
||||
|
||||
# Remove the pin from all conflicts
|
||||
# This is O(n^2), so maybe optimize it.
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin,conflicts in g.items():
|
||||
if pin in conflicts:
|
||||
conflicts.remove(pin)
|
||||
g[other_pin]=conflicts
|
||||
return g
|
||||
|
||||
def vcg_pins_overlap(pins1, pins2, vertical):
|
||||
# Check all the pin pairs on two nets and return a pin
|
||||
# overlap if any pin overlaps vertically
|
||||
for pin1 in pins1:
|
||||
for pin2 in pins2:
|
||||
def vcg_nets_overlap(net1, net2, vertical):
|
||||
"""
|
||||
Check all the pin pairs on two nets and return a pin
|
||||
overlap if any pin overlaps
|
||||
"""
|
||||
|
||||
for pin1 in net1:
|
||||
for pin2 in net2:
|
||||
if vcg_pin_overlap(pin1, pin2, vertical):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def vcg_pin_overlap(pin1, pin2, vertical):
|
||||
# Check for vertical overlap of the two pins
|
||||
|
||||
# Pin 1 must be in the "TOP" set
|
||||
x_overlap = pin1.by() > pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
|
||||
|
||||
# Pin 1 must be in the "LET" set
|
||||
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y-pin2.center().y)<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.
|
||||
|
||||
return (not vertical and x_overlap) or (vertical and y_overlap)
|
||||
# Pin 1 must be in the "BOTTOM" set
|
||||
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
|
||||
|
||||
# Pin 1 must be in the "LEFT" set
|
||||
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y-pin2.center().y)<pitch
|
||||
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
|
||||
return overlaps
|
||||
|
||||
|
||||
|
||||
|
|
@ -777,34 +825,22 @@ class layout(lef.lef):
|
|||
for pin_list in netlist:
|
||||
net_name = "n{}".format(index)
|
||||
index += 1
|
||||
nets[net_name] = []
|
||||
for pin_name in pin_list:
|
||||
pin = pins[pin_name]
|
||||
nets[net_name].append(pin)
|
||||
nets[net_name] = pin_list
|
||||
|
||||
# Find the vertical pin conflicts
|
||||
# FIXME: O(n^2) but who cares for now
|
||||
for net_name1 in nets:
|
||||
vcg[net_name1]=[]
|
||||
if net_name1 not in vcg.keys():
|
||||
vcg[net_name1]=[]
|
||||
for net_name2 in nets:
|
||||
if net_name2 not in vcg.keys():
|
||||
vcg[net_name2]=[]
|
||||
# Skip yourself
|
||||
if net_name1 == net_name2:
|
||||
continue
|
||||
if vcg_pins_overlap(nets[net_name1], nets[net_name2], vertical):
|
||||
try:
|
||||
vcg[net_name2].append(net_name1)
|
||||
except:
|
||||
vcg[net_name2] = [net_name1]
|
||||
if vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical):
|
||||
vcg[net_name2].append(net_name1)
|
||||
|
||||
#FIXME: What if we have a cycle?
|
||||
|
||||
# The starting offset is the first trunk at the top or left
|
||||
# so we must offset from the lower left of the channel placement
|
||||
# in the case of vertical tracks
|
||||
if not vertical:
|
||||
# This will start from top down
|
||||
offset = offset + vector(0,len(nets)*pitch)
|
||||
|
||||
# list of routes to do
|
||||
while vcg:
|
||||
#from pprint import pformat
|
||||
|
|
@ -828,32 +864,30 @@ class layout(lef.lef):
|
|||
# Remove the net from other constriants in the VCG
|
||||
vcg=remove_net_from_graph(net_name, vcg)
|
||||
|
||||
# Add the trunk routes from the bottom up or right to left
|
||||
# Add the trunk routes from the bottom up for horizontal or the left to right for vertical
|
||||
if vertical:
|
||||
self.add_vertical_trunk_route(pin_list, offset, layer_stack, pitch)
|
||||
offset -= vector(pitch,0)
|
||||
offset += vector(pitch,0)
|
||||
else:
|
||||
self.add_horizontal_trunk_route(pin_list, offset, layer_stack, pitch)
|
||||
offset -= vector(0,pitch)
|
||||
offset += vector(0,pitch)
|
||||
|
||||
|
||||
def create_vertical_channel_route(self, netlist, pins, offset,
|
||||
def create_vertical_channel_route(self, netlist, offset,
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
pitch=None):
|
||||
"""
|
||||
Wrapper to create a vertical channel route
|
||||
"""
|
||||
self.create_channel_route(netlist, pins, offset, layer_stack,
|
||||
pitch, vertical=True)
|
||||
self.create_channel_route(netlist, offset, layer_stack, pitch, vertical=True)
|
||||
|
||||
def create_horizontal_channel_route(self, netlist, pins, offset,
|
||||
def create_horizontal_channel_route(self, netlist, offset,
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
pitch=None):
|
||||
"""
|
||||
Wrapper to create a horizontal channel route
|
||||
"""
|
||||
self.create_channel_route(netlist, pins, offset,
|
||||
layer_stack, pitch, vertical=False)
|
||||
self.create_channel_route(netlist, offset, layer_stack, pitch, vertical=False)
|
||||
|
||||
def add_enclosure(self, insts, layer="nwell"):
|
||||
""" Add a layer that surrounds the given instances. Useful
|
||||
|
|
@ -875,21 +909,46 @@ class layout(lef.lef):
|
|||
width=xmax-xmin,
|
||||
height=ymax-ymin)
|
||||
|
||||
def add_power_pin(self, name, loc, rotate=90):
|
||||
"""
|
||||
Add a single power pin from M3 down to M1 at the given center location
|
||||
|
||||
def copy_power_pins(self, inst, name):
|
||||
"""
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=loc,
|
||||
rotate=float(rotate))
|
||||
via=self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
This will copy a power pin if it is on M3. If it is on M1, it will add a power via too.
|
||||
"""
|
||||
pins=inst.get_pins(name)
|
||||
for pin in pins:
|
||||
if pin.layer=="metal3":
|
||||
self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height())
|
||||
elif pin.layer=="metal1":
|
||||
self.add_power_pin(name, pin.center())
|
||||
else:
|
||||
debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name))
|
||||
|
||||
|
||||
|
||||
def add_power_pin(self, name, loc, rotate=90, start_layer="metal1"):
|
||||
"""
|
||||
Add a single power pin from M3 down to M1 at the given center location.
|
||||
The starting layer is specified to determine which vias are needed.
|
||||
"""
|
||||
|
||||
if start_layer=="metal1":
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=loc,
|
||||
rotate=float(rotate))
|
||||
self.add_layout_pin_rect_center(text=name,
|
||||
layer="metal3",
|
||||
offset=loc,
|
||||
width=via.width,
|
||||
height=via.height)
|
||||
if start_layer=="metal1" or start_layer=="metal2":
|
||||
via=self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=loc,
|
||||
rotate=float(rotate))
|
||||
if start_layer=="metal3":
|
||||
self.add_layout_pin_rect_center(text=name,
|
||||
layer="metal3",
|
||||
offset=loc)
|
||||
else:
|
||||
self.add_layout_pin_rect_center(text=name,
|
||||
layer="metal3",
|
||||
offset=loc,
|
||||
width=via.width,
|
||||
height=via.height)
|
||||
|
||||
def add_power_ring(self, bbox):
|
||||
"""
|
||||
|
|
@ -1008,7 +1067,7 @@ class layout(lef.lef):
|
|||
def pdf_write(self, pdf_name):
|
||||
# NOTE: Currently does not work (Needs further research)
|
||||
#self.pdf_name = self.name + ".pdf"
|
||||
debug.info(0, "Writing to %s" % pdf_name)
|
||||
debug.info(0, "Writing to {}".format(pdf_name))
|
||||
pdf = gdsMill.pdfLayout(self.gds)
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import debug
|
|||
import re
|
||||
import os
|
||||
import math
|
||||
import verilog
|
||||
|
||||
class spice(verilog.verilog):
|
||||
class spice():
|
||||
"""
|
||||
This provides a set of useful generic types for hierarchy
|
||||
management. If a module is a custom designed cell, it will read from
|
||||
|
|
@ -91,9 +90,9 @@ class spice(verilog.verilog):
|
|||
group of modules are generated."""
|
||||
|
||||
if (check and (len(self.insts[-1].mod.pins) != len(args))):
|
||||
import pprint
|
||||
modpins_string=pprint.pformat(self.insts[-1].mod.pins)
|
||||
argpins_string=pprint.pformat(args)
|
||||
from pprint import pformat
|
||||
modpins_string=pformat(self.insts[-1].mod.pins)
|
||||
argpins_string=pformat(args)
|
||||
debug.error("Connections: {}".format(modpins_string))
|
||||
debug.error("Connections: {}".format(argpins_string))
|
||||
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
|
||||
|
|
@ -101,9 +100,9 @@ class spice(verilog.verilog):
|
|||
self.conns.append(args)
|
||||
|
||||
if check and (len(self.insts)!=len(self.conns)):
|
||||
import pprint
|
||||
insts_string=pprint.pformat(self.insts)
|
||||
conns_string=pprint.pformat(self.conns)
|
||||
from pprint import pformat
|
||||
insts_string=pformat(self.insts)
|
||||
conns_string=pformat(self.conns)
|
||||
|
||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||
len(self.insts),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from collections import defaultdict
|
|||
class lef:
|
||||
"""
|
||||
SRAM LEF Class open GDS file, read pins information, obstruction
|
||||
and write them to LEF file
|
||||
and write them to LEF file.
|
||||
This is inherited by the sram_base class.
|
||||
"""
|
||||
def __init__(self,layers):
|
||||
# LEF db units per micron
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import debug
|
|||
from tech import GDS, drc
|
||||
from vector import vector
|
||||
from tech import layer
|
||||
import math
|
||||
|
||||
class pin_layout:
|
||||
"""
|
||||
|
|
@ -18,6 +19,10 @@ class pin_layout:
|
|||
self.rect = [vector(rect[0]),vector(rect[1])]
|
||||
# snap the rect to the grid
|
||||
self.rect = [x.snap_to_grid() for x in self.rect]
|
||||
|
||||
debug.check(self.width()>0,"Zero width pin.")
|
||||
debug.check(self.height()>0,"Zero height pin.")
|
||||
|
||||
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
||||
if type(layer_name_num)==int:
|
||||
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
||||
|
|
@ -30,9 +35,26 @@ class pin_layout:
|
|||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
||||
"""
|
||||
override repr function output (don't include
|
||||
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
||||
"""
|
||||
return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1])
|
||||
|
||||
def __hash__(self):
|
||||
""" Implement the hash function for sets etc. """
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
""" Provide a function for ordering items by the ll point """
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
if ll.x < oll.x and ll.y < oll.y:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Check if these are the same pins for duplicate checks """
|
||||
if isinstance(other, self.__class__):
|
||||
|
|
@ -40,13 +62,31 @@ class pin_layout:
|
|||
else:
|
||||
return False
|
||||
|
||||
def bbox(self, pin_list):
|
||||
"""
|
||||
Given a list of layout pins, create a bounding box layout.
|
||||
"""
|
||||
(ll, ur) = self.rect
|
||||
min_x = ll.x
|
||||
max_x = ur.x
|
||||
min_y = ll.y
|
||||
max_y = ur.y
|
||||
|
||||
for pin in pin_list:
|
||||
min_x = min(min_x, pin.ll().x)
|
||||
max_x = max(max_x, pin.ur().x)
|
||||
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)]
|
||||
|
||||
def inflate(self, spacing=None):
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
if not spacing:
|
||||
spacing = drc["{0}_to_{0}".format(self.layer)]
|
||||
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||
|
||||
(ll,ur) = self.rect
|
||||
spacing = vector(spacing, spacing)
|
||||
|
|
@ -54,21 +94,39 @@ class pin_layout:
|
|||
newur = ur + spacing
|
||||
|
||||
return (newll, newur)
|
||||
|
||||
def overlaps(self, other):
|
||||
|
||||
def intersection(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
# Start assuming no overlaps
|
||||
|
||||
min_x = max(ll.x, oll.x)
|
||||
max_x = min(ll.x, oll.x)
|
||||
min_y = max(ll.y, oll.y)
|
||||
max_y = min(ll.y, oll.y)
|
||||
|
||||
return [vector(min_x,min_y),vector(max_x,max_y)]
|
||||
|
||||
def xoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
x_overlaps = False
|
||||
y_overlaps = False
|
||||
# check if self is within other x range
|
||||
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
|
||||
x_overlaps = True
|
||||
# check if other is within self x range
|
||||
if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x):
|
||||
x_overlaps = True
|
||||
|
||||
|
||||
return x_overlaps
|
||||
|
||||
def yoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
y_overlaps = False
|
||||
|
||||
# check if self is within other y range
|
||||
if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y):
|
||||
y_overlaps = True
|
||||
|
|
@ -76,7 +134,63 @@ class pin_layout:
|
|||
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
|
||||
y_overlaps = True
|
||||
|
||||
return y_overlaps
|
||||
|
||||
def xcontains(self, other):
|
||||
""" Check if shape contains the x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
|
||||
return (oll.x >= ll.x and our.x <= ur.x)
|
||||
|
||||
def ycontains(self, other):
|
||||
""" Check if shape contains the y overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
|
||||
return (oll.y >= ll.y and our.y <= ur.y)
|
||||
|
||||
def contains(self, other):
|
||||
""" Check if a shape contains another rectangle """
|
||||
# If it is the same shape entirely, it is contained!
|
||||
if self == other:
|
||||
return True
|
||||
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
return False
|
||||
|
||||
if not self.xcontains(other):
|
||||
return False
|
||||
|
||||
if not self.ycontains(other):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def contained_by_any(self, shape_list):
|
||||
""" Checks if shape is contained by any in the list """
|
||||
for shape in shape_list:
|
||||
if shape.contains(self):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def overlaps(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
return False
|
||||
|
||||
x_overlaps = self.xoverlaps(other)
|
||||
y_overlaps = self.yoverlaps(other)
|
||||
|
||||
return x_overlaps and y_overlaps
|
||||
|
||||
def area(self):
|
||||
""" Return the area. """
|
||||
return self.height()*self.width()
|
||||
|
||||
def height(self):
|
||||
""" Return height. Abs is for pre-normalized value."""
|
||||
return abs(self.rect[1].y-self.rect[0].y)
|
||||
|
|
@ -204,3 +318,163 @@ class pin_layout:
|
|||
magnification=GDS["zoom"],
|
||||
rotate=None)
|
||||
|
||||
|
||||
def compute_overlap(self, other):
|
||||
""" Calculate the rectangular overlap of two rectangles. """
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
|
||||
#ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
|
||||
dy = min(r1_ur.y,r2_ur.y)-max(r1_ll.y,r2_ll.y)
|
||||
dx = min(r1_ur.x,r2_ur.x)-max(r1_ll.x,r2_ll.x)
|
||||
|
||||
if dx>=0 and dy>=0:
|
||||
return [dx,dy]
|
||||
else:
|
||||
return [0,0]
|
||||
|
||||
def distance(self, other):
|
||||
"""
|
||||
Calculate the distance to another pin layout.
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
|
||||
def dist(x1, y1, x2, y2):
|
||||
return 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
|
||||
top = r1_ur.y < r2_ll.y
|
||||
|
||||
if top and left:
|
||||
return dist(r1_ll.x, r1_ur.y, r2_ur.x, r2_ll.y)
|
||||
elif left and bottom:
|
||||
return dist(r1_ll.x, r1_ll.y, r2_ur.x, r2_ur.y)
|
||||
elif bottom and right:
|
||||
return dist(r1_ur.x, r1_ll.y, r2_ll.x, r2_ur.y)
|
||||
elif right and top:
|
||||
return dist(r1_ur.x, r1_ur.y, r2_ll.x, r2_ll.y)
|
||||
elif left:
|
||||
return r1_ll.x - r2_ur.x
|
||||
elif right:
|
||||
return r2_ll.x - r1.ur.x
|
||||
elif bottom:
|
||||
return r1_ll.y - r2_ur.y
|
||||
elif top:
|
||||
return r2_ll.y - r1_ur.y
|
||||
else:
|
||||
# rectangles intersect
|
||||
return 0
|
||||
|
||||
|
||||
def overlap_length(self, other):
|
||||
"""
|
||||
Calculate the intersection segment and determine its length
|
||||
"""
|
||||
|
||||
if self.contains(other):
|
||||
return math.inf
|
||||
elif other.contains(self):
|
||||
return math.inf
|
||||
else:
|
||||
intersections = self.compute_overlap_segment(other)
|
||||
# This is the common case where two pairs of edges overlap
|
||||
# at two points, so just find the distance between those two points
|
||||
if len(intersections)==2:
|
||||
(p1,p2) = intersections
|
||||
return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2))
|
||||
else:
|
||||
# This is where we had a corner intersection or none
|
||||
return 0
|
||||
|
||||
|
||||
def compute_overlap_segment(self, other):
|
||||
"""
|
||||
Calculate the intersection segment of two rectangles
|
||||
(if any)
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
|
||||
# The other corners besides ll and ur
|
||||
r1_ul = vector(r1_ll.x, r1_ur.y)
|
||||
r1_lr = vector(r1_ur.x, r1_ll.y)
|
||||
r2_ul = vector(r2_ll.x, r2_ur.y)
|
||||
r2_lr = vector(r2_ur.x, r2_ll.y)
|
||||
|
||||
from itertools import tee
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
# R1 edges CW
|
||||
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
||||
r1_edges = []
|
||||
for (p,q) in pairwise(r1_cw_points):
|
||||
r1_edges.append([p,q])
|
||||
|
||||
# R2 edges CW
|
||||
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
||||
r2_edges = []
|
||||
for (p,q) in pairwise(r2_cw_points):
|
||||
r2_edges.append([p,q])
|
||||
|
||||
# There are 4 edges on each rectangle
|
||||
# so just brute force check intersection of each
|
||||
# Two pairs of them should intersect
|
||||
intersections = []
|
||||
for r1e in r1_edges:
|
||||
for r2e in r2_edges:
|
||||
i = self.segment_intersection(r1e, r2e)
|
||||
if i:
|
||||
intersections.append(i)
|
||||
|
||||
return intersections
|
||||
|
||||
def on_segment(self, p, q, r):
|
||||
"""
|
||||
Given three co-linear points, determine if q lies on segment pr
|
||||
"""
|
||||
if q.x <= max(p.x, r.x) and \
|
||||
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 False
|
||||
|
||||
def segment_intersection(self, s1, s2):
|
||||
"""
|
||||
Determine the intersection point of two segments
|
||||
Return the a segment if they overlap.
|
||||
Return None if they don't.
|
||||
"""
|
||||
(a,b) = s1
|
||||
(c,d) = s2
|
||||
# Line AB represented as a1x + b1y = c1
|
||||
a1 = b.y - a.y
|
||||
b1 = a.x - b.x
|
||||
c1 = a1*a.x + b1*a.y
|
||||
|
||||
# Line CD represented as a2x + b2y = c2
|
||||
a2 = d.y - c.y
|
||||
b2 = c.x - d.x
|
||||
c2 = a2*c.x + b2*c.y
|
||||
|
||||
determinant = a1*b2 - a2*b1
|
||||
|
||||
if determinant!=0:
|
||||
x = (b2*c1 - b1*c2)/determinant
|
||||
y = (a1*c2 - a2*c1)/determinant
|
||||
|
||||
r = vector(x,y).snap_to_grid()
|
||||
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
||||
return r
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -10,15 +10,17 @@ class route(design):
|
|||
"""
|
||||
Object route (used by the router module)
|
||||
Add a route of minimium metal width between a set of points.
|
||||
The widths are the layer widths of the layer stack.
|
||||
(Vias are in numer of vias.)
|
||||
The wire must be completely rectilinear and the
|
||||
z-dimension of the points refers to the layers (plus via)
|
||||
z-dimension of the points refers to the layers.
|
||||
The points are the center of the wire.
|
||||
This can have non-preferred direction routing.
|
||||
"""
|
||||
|
||||
unique_route_id = 0
|
||||
|
||||
def __init__(self, obj, layer_stack, path):
|
||||
def __init__(self, obj, layer_stack, path, layer_widths=[None,1,None]):
|
||||
name = "route_{0}".format(route.unique_route_id)
|
||||
route.unique_route_id += 1
|
||||
design.__init__(self, name)
|
||||
|
|
@ -26,6 +28,7 @@ class route(design):
|
|||
|
||||
self.obj = obj
|
||||
self.layer_stack = layer_stack
|
||||
self.layer_widths = layer_widths
|
||||
self.path = path
|
||||
|
||||
self.setup_layers()
|
||||
|
|
@ -33,16 +36,16 @@ class route(design):
|
|||
|
||||
|
||||
def setup_layers(self):
|
||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
||||
self.via_layer_name = via_layer
|
||||
|
||||
self.vert_layer_name = vert_layer
|
||||
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
|
||||
|
||||
self.horiz_layer_name = horiz_layer
|
||||
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
|
||||
(self.horiz_layer_name, self.via_layer, self.vert_layer_name) = self.layer_stack
|
||||
(self.horiz_layer_width, self.num_vias, self.vert_layer_width) = self.layer_widths
|
||||
|
||||
if not self.vert_layer_width:
|
||||
self.vert_layer_width = drc("minwidth_{0}".format(self.vert_layer_name))
|
||||
if not self.horiz_layer_width:
|
||||
self.horiz_layer_width = drc("minwidth_{0}".format(self.horiz_layer_name))
|
||||
|
||||
# offset this by 1/2 the via size
|
||||
self.c=contact(self.layer_stack, (1, 1))
|
||||
self.c=contact(self.layer_stack, (self.num_vias, self.num_vias))
|
||||
|
||||
|
||||
def create_wires(self):
|
||||
|
|
@ -59,13 +62,10 @@ class route(design):
|
|||
plist = list(pairwise(self.path))
|
||||
for p0,p1 in plist:
|
||||
if p0.z != p1.z: # via
|
||||
# offset if not rotated
|
||||
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
|
||||
# offset if rotated
|
||||
via_offset = vector(p0.x+0.5*self.c.height,p0.y-0.5*self.c.width)
|
||||
self.obj.add_via(self.layer_stack,via_offset,rotate=90)
|
||||
via_size = [self.num_vias]*2
|
||||
self.obj.add_via_center(self.layer_stack,vector(p0.x,p0.y),size=via_size,rotate=90)
|
||||
elif p0.x != p1.x and p0.y != p1.y: # diagonal!
|
||||
debug.error("Non-changing direction!")
|
||||
debug.error("Diagonal route! {}".format(self.path),-3)
|
||||
else:
|
||||
# this will draw an extra corner at the end but that is ok
|
||||
self.draw_corner_wire(p1)
|
||||
|
|
@ -79,14 +79,36 @@ class route(design):
|
|||
self.draw_corner_wire(plist[-1][1])
|
||||
|
||||
|
||||
|
||||
def get_layer_width(self, layer_zindex):
|
||||
"""
|
||||
Return the layer width
|
||||
"""
|
||||
if layer_zindex==0:
|
||||
return self.horiz_layer_width
|
||||
elif layer_zindex==1:
|
||||
return self.vert_layer_width
|
||||
else:
|
||||
debug.error("Incorrect layer zindex.",-1)
|
||||
|
||||
def get_layer_name(self, layer_zindex):
|
||||
"""
|
||||
Return the layer name
|
||||
"""
|
||||
if layer_zindex==0:
|
||||
return self.horiz_layer_name
|
||||
elif layer_zindex==1:
|
||||
return self.vert_layer_name
|
||||
else:
|
||||
debug.error("Incorrect layer zindex.",-1)
|
||||
|
||||
|
||||
def draw_wire(self, p0, p1):
|
||||
"""
|
||||
This draws a straight wire with layer_minwidth
|
||||
"""
|
||||
layer_name = self.layer_stack[2*p0.z]
|
||||
layer_width = drc["minwidth_{0}".format(layer_name)]
|
||||
|
||||
layer_width = self.get_layer_width(p0.z)
|
||||
layer_name = self.get_layer_name(p0.z)
|
||||
|
||||
# always route left to right or bottom to top
|
||||
if p0.z != p1.z:
|
||||
|
|
@ -120,8 +142,8 @@ class route(design):
|
|||
""" This function adds the corner squares since the center
|
||||
line convention only draws to the center of the corner."""
|
||||
|
||||
layer_name = self.layer_stack[2*p0.z]
|
||||
layer_width = drc["minwidth_{0}".format(layer_name)]
|
||||
layer_width = self.get_layer_width(p0.z)
|
||||
layer_name = self.get_layer_name(p0.z)
|
||||
offset = vector(p0.x-0.5*layer_width,p0.y-0.5*layer_width)
|
||||
self.obj.add_rect(layer=layer_name,
|
||||
offset=offset,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import gdsMill
|
|||
import tech
|
||||
import math
|
||||
import globals
|
||||
import debug
|
||||
from vector import vector
|
||||
from pin_layout import pin_layout
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ def get_gds_size(name, gds_filename, units, layer):
|
|||
Open a GDS file and return the size from either the
|
||||
bounding box or a border layer.
|
||||
"""
|
||||
debug.info(4,"Creating VLSI layout for {}".format(name))
|
||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||
reader.loadFromFile(gds_filename)
|
||||
|
|
@ -72,6 +74,7 @@ def get_gds_size(name, gds_filename, units, layer):
|
|||
cell = {}
|
||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||
if measure_result == None:
|
||||
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
|
||||
measure_result = cell_vlsi.measureSize(name)
|
||||
# returns width,height
|
||||
return measure_result
|
||||
|
|
@ -85,9 +88,9 @@ def get_libcell_size(name, units, layer):
|
|||
return(get_gds_size(name, cell_gds, units, layer))
|
||||
|
||||
|
||||
def get_gds_pins(pin_list, name, gds_filename, units, layer):
|
||||
def get_gds_pins(pin_names, name, gds_filename, units):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Open a GDS file and find the pins in pin_names as text on a given layer.
|
||||
Return these as a rectangle layer pair for each pin.
|
||||
"""
|
||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||
|
|
@ -95,23 +98,23 @@ def get_gds_pins(pin_list, name, gds_filename, units, layer):
|
|||
reader.loadFromFile(gds_filename)
|
||||
|
||||
cell = {}
|
||||
for pin in pin_list:
|
||||
cell[str(pin)]=[]
|
||||
label_list=cell_vlsi.getPinShapeByLabel(str(pin))
|
||||
for label in label_list:
|
||||
(name,layer,boundary)=label
|
||||
for pin_name in pin_names:
|
||||
cell[str(pin_name)]=[]
|
||||
pin_list=cell_vlsi.getPinShape(str(pin_name))
|
||||
for pin_shape in pin_list:
|
||||
(layer,boundary)=pin_shape
|
||||
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
||||
# this is a list because other cells/designs may have must-connect pins
|
||||
cell[str(pin)].append(pin_layout(pin, rect, layer))
|
||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
|
||||
return cell
|
||||
|
||||
def get_libcell_pins(pin_list, name, units, layer):
|
||||
def get_libcell_pins(pin_list, name, units):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Return these as a rectangle layer pair for each pin.
|
||||
"""
|
||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||
return(get_gds_pins(pin_list, name, cell_gds, units, layer))
|
||||
return(get_gds_pins(pin_list, name, cell_gds, units))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,24 +11,24 @@ class vector():
|
|||
concise vector operations, output, and other more complex
|
||||
data structures like lists.
|
||||
"""
|
||||
def __init__(self, x, y=None):
|
||||
def __init__(self, x, y=0):
|
||||
""" init function support two init method"""
|
||||
# will take single input as a coordinate
|
||||
if y==None:
|
||||
self.x = x[0]
|
||||
self.y = x[1]
|
||||
if isinstance(x, (list,tuple,vector)):
|
||||
self.x = float(x[0])
|
||||
self.y = float(x[1])
|
||||
#will take two inputs as the values of a coordinate
|
||||
else:
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "["+str(self.x)+","+str(self.y)+"]"
|
||||
return "v["+str(self.x)+","+str(self.y)+"]"
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "["+str(self.x)+","+str(self.y)+"]"
|
||||
return "v["+str(self.x)+","+str(self.y)+"]"
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""
|
||||
|
|
@ -36,12 +36,12 @@ class vector():
|
|||
can set value by vector[index]=value
|
||||
"""
|
||||
if index==0:
|
||||
self.x=value
|
||||
self.x=float(value)
|
||||
elif index==1:
|
||||
self.y=value
|
||||
self.y=float(value)
|
||||
else:
|
||||
self.x=value[0]
|
||||
self.y=value[1]
|
||||
self.x=float(value[0])
|
||||
self.y=float(value[1])
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
|
|
@ -84,6 +84,14 @@ class vector():
|
|||
"""
|
||||
return vector(other[0]- self.x, other[1] - self.y)
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Override - function (hash)
|
||||
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
||||
break things.
|
||||
"""
|
||||
return hash((self.x,self.y))
|
||||
|
||||
def snap_to_grid(self):
|
||||
self.x = self.snap_offset_to_grid(self.x)
|
||||
self.y = self.snap_offset_to_grid(self.y)
|
||||
|
|
|
|||
|
|
@ -1,60 +1,165 @@
|
|||
import debug
|
||||
|
||||
class verilog:
|
||||
""" Create a behavioral Verilog file for simulation."""
|
||||
|
||||
"""
|
||||
Create a behavioral Verilog file for simulation.
|
||||
This is inherited by the sram_base class.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def verilog_write(self,verilog_name):
|
||||
""" Write a behavioral Verilog model. """
|
||||
|
||||
self.vf = open(verilog_name, "w")
|
||||
|
||||
self.vf.write("// OpenRAM SRAM model\n")
|
||||
self.vf.write("// Words: {0}\n".format(self.num_words))
|
||||
self.vf.write("// Word size: {0}\n\n".format(self.word_size))
|
||||
|
||||
self.vf.write("module {0}(DATA,ADDR,CSb,WEb,OEb,clk);\n".format(self.name))
|
||||
self.vf.write("\n")
|
||||
|
||||
self.vf.write("module {0}(\n".format(self.name))
|
||||
for port in self.all_ports:
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write("// Port {0}: RW\n".format(port))
|
||||
elif port in self.read_ports:
|
||||
self.vf.write("// Port {0}: R\n".format(port))
|
||||
elif port in self.write_ports:
|
||||
self.vf.write("// Port {0}: W\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" clk{0},csb{0},web{0},ADDR{0},DIN{0},DOUT{0}".format(port))
|
||||
elif port in self.write_ports:
|
||||
self.vf.write(" clk{0},csb{0},ADDR{0},DIN{0}".format(port))
|
||||
elif port in self.read_ports:
|
||||
self.vf.write(" clk{0},csb{0},ADDR{0},DOUT{0}".format(port))
|
||||
# Continue for every port on a new line
|
||||
if port != self.all_ports[-1]:
|
||||
self.vf.write(",\n")
|
||||
self.vf.write("\n );\n\n")
|
||||
|
||||
self.vf.write(" parameter DATA_WIDTH = {0} ;\n".format(self.word_size))
|
||||
self.vf.write(" parameter ADDR_WIDTH = {0} ;\n".format(self.addr_size))
|
||||
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
|
||||
self.vf.write(" // FIXME: This delay is arbitrary.\n")
|
||||
self.vf.write(" parameter DELAY = 3 ;\n")
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" inout [DATA_WIDTH-1:0] DATA;\n")
|
||||
self.vf.write(" input [ADDR_WIDTH-1:0] ADDR;\n")
|
||||
self.vf.write(" input CSb; // active low chip select\n")
|
||||
self.vf.write(" input WEb; // active low write control\n")
|
||||
self.vf.write(" input OEb; // active output enable\n")
|
||||
self.vf.write(" input clk; // clock\n")
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" reg [DATA_WIDTH-1:0] data_out ;\n")
|
||||
self.vf.write(" reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];\n")
|
||||
|
||||
for port in self.all_ports:
|
||||
self.add_inputs_outputs(port)
|
||||
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // Tri-State Buffer control\n")
|
||||
self.vf.write(" // output : When WEb = 1, oeb = 0, csb = 0\n")
|
||||
self.vf.write(" assign DATA = (!CSb && !OEb && WEb) ? data_out : {0}'bz;\n".format(self.word_size))
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // Memory Write Block\n")
|
||||
self.vf.write(" // Write Operation : When WEb = 0, CSb = 0\n")
|
||||
self.vf.write(" always @ (posedge clk)\n")
|
||||
self.vf.write(" begin : MEM_WRITE\n")
|
||||
self.vf.write(" if ( !CSb && !WEb ) begin\n")
|
||||
self.vf.write(" mem[ADDR] = DATA;\n")
|
||||
self.vf.write(" $display($time,\" Writing %m ABUS=%b DATA=%b\",ADDR,DATA);\n")
|
||||
self.vf.write(" end\n")
|
||||
self.vf.write(" end\n\n")
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // Memory Read Block\n")
|
||||
self.vf.write(" // Read Operation : When WEb = 1, CSb = 0\n")
|
||||
self.vf.write(" always @ (posedge clk)\n")
|
||||
self.vf.write(" begin : MEM_READ\n")
|
||||
self.vf.write(" if (!CSb && WEb) begin\n")
|
||||
self.vf.write(" data_out <= #(DELAY) mem[ADDR];\n")
|
||||
self.vf.write(" $display($time,\" Reading %m ABUS=%b DATA=%b\",ADDR,mem[ADDR]);\n")
|
||||
self.vf.write(" end\n")
|
||||
self.vf.write(" end\n")
|
||||
|
||||
for port in self.all_ports:
|
||||
self.register_inputs(port)
|
||||
|
||||
# This is the memory array itself
|
||||
self.vf.write("reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];\n")
|
||||
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
self.add_write_block(port)
|
||||
if port in self.read_ports:
|
||||
self.add_read_block(port)
|
||||
|
||||
self.vf.write("\n")
|
||||
self.vf.write("endmodule\n")
|
||||
|
||||
|
||||
self.vf.close()
|
||||
|
||||
|
||||
def register_inputs(self, port):
|
||||
"""
|
||||
Register the control signal, address and data inputs.
|
||||
"""
|
||||
self.add_regs(port)
|
||||
self.add_flops(port)
|
||||
|
||||
def add_regs(self, port):
|
||||
"""
|
||||
Create the input regs for the given port.
|
||||
"""
|
||||
self.vf.write(" reg csb{0}_reg;\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" reg web{0}_reg;\n".format(port))
|
||||
self.vf.write(" reg [ADDR_WIDTH-1:0] ADDR{0}_reg;\n".format(port))
|
||||
if port in self.write_ports:
|
||||
self.vf.write(" reg [DATA_WIDTH-1:0] DIN{0}_reg;\n".format(port))
|
||||
if port in self.read_ports:
|
||||
self.vf.write(" reg [DATA_WIDTH-1:0] DOUT{0};\n".format(port))
|
||||
|
||||
def add_flops(self, port):
|
||||
"""
|
||||
Add the flop behavior logic for a port.
|
||||
"""
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // All inputs are registers\n")
|
||||
self.vf.write(" always @(posedge clk{0})\n".format(port))
|
||||
self.vf.write(" begin\n")
|
||||
self.vf.write(" csb{0}_reg = csb{0};\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" web{0}_reg = web{0};\n".format(port))
|
||||
self.vf.write(" ADDR{0}_reg = ADDR{0};\n".format(port))
|
||||
if port in self.write_ports:
|
||||
self.vf.write(" DIN{0}_reg = DIN{0};\n".format(port))
|
||||
if port in self.read_ports:
|
||||
self.vf.write(" DOUT{0} = {1}'bx;\n".format(port,self.word_size))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg && web{0}_reg ) \n".format(port))
|
||||
self.vf.write(" $display($time,\" Reading %m ADDR{0}=%b DOUT{0}=%b\",ADDR{0}_reg,mem[ADDR{0}_reg]);\n".format(port))
|
||||
elif port in self.read_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg ) \n".format(port))
|
||||
self.vf.write(" $display($time,\" Reading %m ADDR{0}=%b DOUT{0}=%b\",ADDR{0}_reg,mem[ADDR{0}_reg]);\n".format(port))
|
||||
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port))
|
||||
self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port))
|
||||
elif port in self.write_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg )\n".format(port))
|
||||
self.vf.write(" $display($time,\" Writing %m ADDR{0}=%b DIN{0}=%b\",ADDR{0}_reg,DIN{0}_reg);\n".format(port))
|
||||
self.vf.write(" end\n\n")
|
||||
|
||||
|
||||
def add_inputs_outputs(self, port):
|
||||
"""
|
||||
Add the module input and output declaration for a port.
|
||||
"""
|
||||
self.vf.write(" input clk{0}; // clock\n".format(port))
|
||||
self.vf.write(" input csb{0}; // active low chip select\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" input web{0}; // active low write control\n".format(port))
|
||||
self.vf.write(" input [ADDR_WIDTH-1:0] ADDR{0};\n".format(port))
|
||||
if port in self.write_ports:
|
||||
self.vf.write(" input [DATA_WIDTH-1:0] DIN{0};\n".format(port))
|
||||
if port in self.read_ports:
|
||||
self.vf.write(" output [DATA_WIDTH-1:0] DOUT{0};\n".format(port))
|
||||
|
||||
def add_write_block(self, port):
|
||||
"""
|
||||
Add a write port block. Multiple simultaneous writes to the same address
|
||||
have arbitrary priority and are not allowed.
|
||||
"""
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // Memory Write Block Port {0}\n".format(port))
|
||||
self.vf.write(" // Write Operation : When web{0} = 0, csb{0} = 0\n".format(port))
|
||||
self.vf.write(" always @ (negedge clk{0})\n".format(port))
|
||||
self.vf.write(" begin : MEM_WRITE{0}\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port))
|
||||
else:
|
||||
self.vf.write(" if (!csb{0}_reg)\n".format(port))
|
||||
self.vf.write(" mem[ADDR{0}_reg] = DIN{0}_reg;\n".format(port))
|
||||
self.vf.write(" end\n")
|
||||
|
||||
def add_read_block(self, port):
|
||||
"""
|
||||
Add a read port block.
|
||||
"""
|
||||
self.vf.write("\n")
|
||||
self.vf.write(" // Memory Read Block Port {0}\n".format(port))
|
||||
self.vf.write(" // Read Operation : When web{0} = 1, csb{0} = 0\n".format(port))
|
||||
self.vf.write(" always @ (negedge clk{0})\n".format(port))
|
||||
self.vf.write(" begin : MEM_READ{0}\n".format(port))
|
||||
if port in self.readwrite_ports:
|
||||
self.vf.write(" if (!csb{0}_reg && web{0}_reg)\n".format(port))
|
||||
else:
|
||||
self.vf.write(" if (!csb{0}_reg)\n".format(port))
|
||||
self.vf.write(" DOUT{0} <= #(DELAY) mem[ADDR{0}_reg];\n".format(port))
|
||||
self.vf.write(" end\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -33,14 +33,14 @@ class wire(path):
|
|||
self.via_layer_name = via_layer
|
||||
|
||||
self.vert_layer_name = vert_layer
|
||||
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
|
||||
self.vert_layer_width = drc("minwidth_{0}".format(vert_layer))
|
||||
|
||||
self.horiz_layer_name = horiz_layer
|
||||
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
|
||||
self.horiz_layer_width = drc("minwidth_{0}".format(horiz_layer))
|
||||
via_connect = contact(self.layer_stack,
|
||||
(1, 1))
|
||||
self.node_to_node = [drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.width,
|
||||
drc["minwidth_" + str(self.horiz_layer_name)] + via_connect.height]
|
||||
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
||||
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
|
||||
|
||||
# create a 1x1 contact
|
||||
def create_vias(self):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer
|
||||
from tech import GDS,layer,parameter,drc
|
||||
|
||||
class bitcell(design.design):
|
||||
"""
|
||||
|
|
@ -13,7 +13,7 @@ class bitcell(design.design):
|
|||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
|
||||
|
||||
def __init__(self):
|
||||
design.design.__init__(self, "cell_6t")
|
||||
|
|
@ -38,9 +38,9 @@ class bitcell(design.design):
|
|||
|
||||
def list_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
|
||||
bitcell_pins = ["bl[{0}]".format(col),
|
||||
"br[{0}]".format(col),
|
||||
"wl[{0}]".format(row),
|
||||
bitcell_pins = ["bl_{0}".format(col),
|
||||
"br_{0}".format(col),
|
||||
"wl_{0}".format(row),
|
||||
"vdd",
|
||||
"gnd"]
|
||||
return bitcell_pins
|
||||
|
|
@ -65,26 +65,6 @@ class bitcell(design.design):
|
|||
column_pins = ["br"]
|
||||
return column_pins
|
||||
|
||||
def list_read_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with read ports """
|
||||
column_pins = ["bl"]
|
||||
return column_pins
|
||||
|
||||
def list_read_br_names(self):
|
||||
""" Creates a list of br pin names associated with read ports """
|
||||
column_pins = ["br"]
|
||||
return column_pins
|
||||
|
||||
def list_write_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with write ports """
|
||||
column_pins = ["bl"]
|
||||
return column_pins
|
||||
|
||||
def list_write_br_names(self):
|
||||
""" Creates a list of br pin names asscociated with write ports"""
|
||||
column_pins = ["br"]
|
||||
return column_pins
|
||||
|
||||
def analytical_power(self, proc, vdd, temp, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
|
|
@ -93,3 +73,9 @@ class bitcell(design.design):
|
|||
total_power = self.return_power(dynamic, leakage)
|
||||
return total_power
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#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 related gates and dividing by the minimum width.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer,parameter,drc
|
||||
|
||||
class bitcell_1rw_1r(design.design):
|
||||
"""
|
||||
A single bit cell (6T, 8T, etc.) This module implements the
|
||||
single memory cell used in the design. It is a hand-made cell, so
|
||||
the layout and netlist should be available in the technology
|
||||
library.
|
||||
"""
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"])
|
||||
|
||||
def __init__(self):
|
||||
design.design.__init__(self, "cell_1rw_1r")
|
||||
debug.info(2, "Create bitcell with 1RW and 1R Port")
|
||||
|
||||
self.width = bitcell_1rw_1r.width
|
||||
self.height = bitcell_1rw_1r.height
|
||||
self.pin_map = bitcell_1rw_1r.pin_map
|
||||
|
||||
def analytical_delay(self, slew, load=0, swing = 0.5):
|
||||
# delay of bit cell is not like a driver(from WL)
|
||||
# so the slew used should be 0
|
||||
# it should not be slew dependent?
|
||||
# because the value is there
|
||||
# the delay is only over half transsmission gate
|
||||
from tech import spice
|
||||
r = spice["min_tx_r"]*3
|
||||
c_para = spice["min_tx_drain_c"]
|
||||
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
|
||||
return result
|
||||
|
||||
|
||||
def list_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
|
||||
bitcell_pins = ["bl0_{0}".format(col),
|
||||
"br0_{0}".format(col),
|
||||
"bl1_{0}".format(col),
|
||||
"br1_{0}".format(col),
|
||||
"wl0_{0}".format(row),
|
||||
"wl1_{0}".format(row),
|
||||
"vdd",
|
||||
"gnd"]
|
||||
return bitcell_pins
|
||||
|
||||
def list_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
row_pins = ["wl0", "wl1"]
|
||||
return row_pins
|
||||
|
||||
def list_all_bitline_names(self):
|
||||
""" Creates a list of all bitline pin names (both bl and br) """
|
||||
column_pins = ["bl0", "br0", "bl1", "br1"]
|
||||
return column_pins
|
||||
|
||||
def list_all_bl_names(self):
|
||||
""" Creates a list of all bl pins names """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
def list_all_br_names(self):
|
||||
""" Creates a list of all br pins names """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
def list_read_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with read ports """
|
||||
column_pins = ["bl0", "bl1"]
|
||||
return column_pins
|
||||
|
||||
def list_read_br_names(self):
|
||||
""" Creates a list of br pin names associated with read ports """
|
||||
column_pins = ["br0", "br1"]
|
||||
return column_pins
|
||||
|
||||
def list_write_bl_names(self):
|
||||
""" Creates a list of bl pin names associated with write ports """
|
||||
column_pins = ["bl0"]
|
||||
return column_pins
|
||||
|
||||
def list_write_br_names(self):
|
||||
""" Creates a list of br pin names asscociated with write ports"""
|
||||
column_pins = ["br0"]
|
||||
return column_pins
|
||||
|
||||
def analytical_power(self, proc, vdd, temp, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
leakage = spice["bitcell_leakage"]
|
||||
dynamic = 0 #temporary
|
||||
total_power = self.return_power(dynamic, leakage)
|
||||
return total_power
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#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 related gates and dividing by the minimum width.
|
||||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
|
@ -0,0 +1,896 @@
|
|||
import contact
|
||||
import design
|
||||
import debug
|
||||
from tech import drc, parameter, spice
|
||||
from vector import vector
|
||||
from ptx import ptx
|
||||
from globals import OPTS
|
||||
|
||||
class pbitcell(design.design):
|
||||
"""
|
||||
This module implements a parametrically sized multi-port bitcell,
|
||||
with a variable number of read/write, write, and read ports
|
||||
"""
|
||||
|
||||
def __init__(self, replica_bitcell=False):
|
||||
|
||||
self.num_rw_ports = OPTS.num_rw_ports
|
||||
self.num_w_ports = OPTS.num_w_ports
|
||||
self.num_r_ports = OPTS.num_r_ports
|
||||
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
|
||||
|
||||
self.replica_bitcell = replica_bitcell
|
||||
|
||||
if self.replica_bitcell:
|
||||
name = "replica_pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
|
||||
else:
|
||||
name = "pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
|
||||
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
|
||||
self.num_w_ports,
|
||||
self.num_r_ports))
|
||||
|
||||
self.create_netlist()
|
||||
# We must always create the bitcell layout because some transistor sizes in the other netlists depend on it
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_storage()
|
||||
|
||||
if(self.num_rw_ports > 0):
|
||||
self.create_readwrite_ports()
|
||||
if(self.num_w_ports > 0):
|
||||
self.create_write_ports()
|
||||
if(self.num_r_ports > 0):
|
||||
self.create_read_ports()
|
||||
|
||||
def create_layout(self):
|
||||
self.calculate_spacing()
|
||||
self.calculate_postions()
|
||||
|
||||
self.place_storage()
|
||||
self.route_storage()
|
||||
|
||||
self.route_rails()
|
||||
|
||||
if(self.num_rw_ports > 0):
|
||||
self.place_readwrite_ports()
|
||||
self.route_readwrite_access()
|
||||
if(self.num_w_ports > 0):
|
||||
self.place_write_ports()
|
||||
self.route_write_access()
|
||||
if(self.num_r_ports > 0):
|
||||
self.place_read_ports()
|
||||
self.route_read_access()
|
||||
self.extend_well()
|
||||
|
||||
self.route_wordlines()
|
||||
self.route_bitlines()
|
||||
self.route_supply()
|
||||
|
||||
if self.replica_bitcell:
|
||||
self.route_rbc_short()
|
||||
|
||||
# in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible
|
||||
# this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though
|
||||
if not OPTS.netlist_only:
|
||||
self.offset_all_coordinates()
|
||||
gnd_overlap = vector(0, 0.5*contact.well.width)
|
||||
self.translate_all(gnd_overlap)
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
""" add pins and set names for bitlines and wordlines """
|
||||
self.rw_bl_names = []
|
||||
self.rw_br_names = []
|
||||
self.w_bl_names = []
|
||||
self.w_br_names = []
|
||||
self.r_bl_names = []
|
||||
self.r_br_names = []
|
||||
self.rw_wl_names = []
|
||||
self.w_wl_names = []
|
||||
self.r_wl_names = []
|
||||
port = 0
|
||||
|
||||
for k in range(self.num_rw_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.rw_bl_names.append("bl{}".format(port))
|
||||
self.rw_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_w_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.w_bl_names.append("bl{}".format(port))
|
||||
self.w_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_r_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.r_bl_names.append("bl{}".format(port))
|
||||
self.r_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
|
||||
port = 0
|
||||
for k in range(self.num_rw_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.rw_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_w_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.w_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_r_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.r_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
# if this is a replica bitcell, replace the instances of Q_bar with vdd
|
||||
if self.replica_bitcell:
|
||||
self.Q_bar = "vdd"
|
||||
else:
|
||||
self.Q_bar = "Q_bar"
|
||||
|
||||
def add_modules(self):
|
||||
""" Determine size of transistors and add ptx modules """
|
||||
# if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports
|
||||
if(self.num_rw_ports > 0):
|
||||
inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"]
|
||||
inverter_pmos_width = parameter["6T_inv_pmos_size"]
|
||||
readwrite_nmos_width = parameter["6T_access_size"]
|
||||
write_nmos_width = parameter["6T_access_size"]
|
||||
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||
|
||||
# if there are no read/write ports, then the inverter nmos is statically sized for the dual port case
|
||||
else:
|
||||
inverter_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||
inverter_pmos_width = parameter["6T_inv_pmos_size"]
|
||||
readwrite_nmos_width = parameter["6T_access_size"]
|
||||
write_nmos_width = parameter["6T_access_size"]
|
||||
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
|
||||
|
||||
# create ptx for inverter transistors
|
||||
self.inverter_nmos = ptx(width=inverter_nmos_width,
|
||||
tx_type="nmos")
|
||||
self.add_mod(self.inverter_nmos)
|
||||
|
||||
self.inverter_pmos = ptx(width=inverter_pmos_width,
|
||||
tx_type="pmos")
|
||||
self.add_mod(self.inverter_pmos)
|
||||
|
||||
# create ptx for readwrite transitors
|
||||
self.readwrite_nmos = ptx(width=readwrite_nmos_width,
|
||||
tx_type="nmos")
|
||||
self.add_mod(self.readwrite_nmos)
|
||||
|
||||
# create ptx for write transitors
|
||||
self.write_nmos = ptx(width=write_nmos_width,
|
||||
tx_type="nmos")
|
||||
self.add_mod(self.write_nmos)
|
||||
|
||||
# create ptx for read transistors
|
||||
self.read_nmos = ptx(width=read_nmos_width,
|
||||
tx_type="nmos")
|
||||
self.add_mod(self.read_nmos)
|
||||
|
||||
def calculate_spacing(self):
|
||||
""" Calculate transistor spacings """
|
||||
# calculate metal contact extensions over transistor active
|
||||
readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
||||
write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
|
||||
read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height)
|
||||
max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension)
|
||||
|
||||
# y-offset for the access transistor's gate contact
|
||||
self.gate_contact_yoffset = max_contact_extension + self.m2_space + 0.5*max(contact.poly.height, contact.m1m2.height)
|
||||
|
||||
# y-position of access transistors
|
||||
self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset
|
||||
|
||||
# y-position of inverter nmos
|
||||
self.inverter_nmos_ypos = self.port_ypos
|
||||
|
||||
# spacing between ports (same for read/write and write ports)
|
||||
self.bitline_offset = -0.5*self.readwrite_nmos.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width
|
||||
m2_constraint = self.bitline_offset + self.m2_space + 0.5*contact.m1m2.height - 0.5*self.readwrite_nmos.active_width
|
||||
self.write_port_spacing = max(self.active_space, self.m1_space, m2_constraint)
|
||||
self.read_port_spacing = self.bitline_offset + self.m2_space
|
||||
|
||||
# spacing between cross coupled inverters
|
||||
self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space
|
||||
|
||||
# calculations related to inverter connections
|
||||
inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
|
||||
inverter_nmos_contact_extension = 0.5*(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
|
||||
self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ self.poly_to_polycontact + 2*contact.poly.width \
|
||||
+ self.m1_space + inverter_pmos_contact_extension
|
||||
self.cross_couple_lower_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ 0.5*contact.poly.width
|
||||
self.cross_couple_upper_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
|
||||
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
|
||||
+ self.poly_to_polycontact \
|
||||
+ 1.5*contact.poly.width
|
||||
|
||||
# spacing between wordlines (and gnd)
|
||||
self.rowline_spacing = self.m1_space + contact.m1m2.width
|
||||
self.rowline_offset = -0.5*self.m1_width
|
||||
|
||||
# spacing for vdd
|
||||
implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - self.m1_width)
|
||||
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
|
||||
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
|
||||
|
||||
# read port dimensions
|
||||
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
|
||||
self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction
|
||||
|
||||
def calculate_postions(self):
|
||||
""" Calculate positions that describe the edges and dimensions of the cell """
|
||||
self.botmost_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing
|
||||
self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
|
||||
|
||||
self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \
|
||||
- self.num_rw_ports*(self.readwrite_nmos.active_width + self.write_port_spacing) \
|
||||
- self.num_w_ports*(self.write_nmos.active_width + self.write_port_spacing) \
|
||||
- self.num_r_ports*(self.read_port_width + self.read_port_spacing) \
|
||||
- self.bitline_offset - 0.5*contact.m1m2.width
|
||||
|
||||
self.width = -2*self.leftmost_xpos
|
||||
self.height = self.topmost_ypos - self.botmost_ypos
|
||||
|
||||
self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos)
|
||||
|
||||
def create_storage(self):
|
||||
"""
|
||||
Creates the crossed coupled inverters that act as storage for the bitcell.
|
||||
The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar".
|
||||
"""
|
||||
# create active for nmos
|
||||
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
|
||||
mod=self.inverter_nmos)
|
||||
self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"])
|
||||
|
||||
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
|
||||
mod=self.inverter_nmos)
|
||||
self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"])
|
||||
|
||||
# create active for pmos
|
||||
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
|
||||
mod=self.inverter_pmos)
|
||||
self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"])
|
||||
|
||||
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
|
||||
mod=self.inverter_pmos)
|
||||
self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"])
|
||||
|
||||
def place_storage(self):
|
||||
""" Places the transistors for the crossed coupled inverters in the bitcell """
|
||||
# calculate transistor offsets
|
||||
left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width
|
||||
right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing
|
||||
inverter_pmos_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap
|
||||
|
||||
# create active for nmos
|
||||
self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos])
|
||||
self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos])
|
||||
|
||||
# create active for pmos
|
||||
self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos])
|
||||
self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos])
|
||||
|
||||
# update furthest left and right transistor edges (this will propagate to further transistor offset calculations)
|
||||
self.left_building_edge = left_inverter_xpos
|
||||
self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width
|
||||
|
||||
def route_storage(self):
|
||||
""" Routes inputs and outputs of inverters to cross couple them """
|
||||
# connect input (gate) of inverters
|
||||
self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()])
|
||||
self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()])
|
||||
|
||||
# connect output (drain/source) of inverters
|
||||
self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width)
|
||||
self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_width)
|
||||
|
||||
# add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar)
|
||||
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, self.cross_couple_upper_ypos)
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=contact_offset_left,
|
||||
rotate=90)
|
||||
|
||||
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos)
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=contact_offset_right,
|
||||
rotate=90)
|
||||
|
||||
# connect contacts to gate poly (cross couple connections)
|
||||
gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y)
|
||||
self.add_path("poly", [contact_offset_left, gate_offset_right])
|
||||
|
||||
gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y)
|
||||
self.add_path("poly", [contact_offset_right, gate_offset_left])
|
||||
|
||||
def route_rails(self):
|
||||
""" Adds gnd and vdd rails and connects them to the inverters """
|
||||
# Add rails for vdd and gnd
|
||||
gnd_ypos = self.rowline_offset - self.total_ports*self.rowline_spacing
|
||||
self.gnd_position = vector(0, gnd_ypos)
|
||||
self.add_rect_center(layer="metal1",
|
||||
offset=self.gnd_position,
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
self.add_power_pin("gnd", vector(0,gnd_ypos))
|
||||
|
||||
|
||||
vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
|
||||
self.vdd_position = vector(0, vdd_ypos)
|
||||
self.add_rect_center(layer="metal1",
|
||||
offset=self.vdd_position,
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
self.add_power_pin("vdd", vector(0,vdd_ypos))
|
||||
|
||||
def create_readwrite_ports(self):
|
||||
"""
|
||||
Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell.
|
||||
A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor.
|
||||
The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q).
|
||||
In a write operation, driving RWBL high or low sets the value of the cell.
|
||||
In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell.
|
||||
This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar.
|
||||
"""
|
||||
# define read/write transistor variables as empty arrays based on the number of read/write ports
|
||||
self.readwrite_nmos_left = [None] * self.num_rw_ports
|
||||
self.readwrite_nmos_right = [None] * self.num_rw_ports
|
||||
|
||||
# iterate over the number of read/write ports
|
||||
for k in range(0,self.num_rw_ports):
|
||||
# add read/write transistors
|
||||
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
|
||||
mod=self.readwrite_nmos)
|
||||
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"])
|
||||
|
||||
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
|
||||
mod=self.readwrite_nmos)
|
||||
self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"])
|
||||
|
||||
def place_readwrite_ports(self):
|
||||
""" Places read/write ports in the bit cell """
|
||||
# define read/write transistor variables as empty arrays based on the number of read/write ports
|
||||
self.rwwl_positions = [None] * self.num_rw_ports
|
||||
self.rwbl_positions = [None] * self.num_rw_ports
|
||||
self.rwbr_positions = [None] * self.num_rw_ports
|
||||
|
||||
# iterate over the number of read/write ports
|
||||
for k in range(0,self.num_rw_ports):
|
||||
# calculate read/write transistor offsets
|
||||
left_readwrite_transistor_xpos = self.left_building_edge \
|
||||
- (k+1)*self.write_port_spacing \
|
||||
- (k+1)*self.readwrite_nmos.active_width
|
||||
|
||||
right_readwrite_transistor_xpos = self.right_building_edge \
|
||||
+ (k+1)*self.write_port_spacing \
|
||||
+ k*self.readwrite_nmos.active_width
|
||||
|
||||
# place read/write transistors
|
||||
self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos])
|
||||
|
||||
self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos])
|
||||
|
||||
# add pin for RWWL
|
||||
rwwl_ypos = self.rowline_offset - k*self.rowline_spacing
|
||||
self.rwwl_positions[k] = vector(0, rwwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
|
||||
layer="metal1",
|
||||
offset=self.rwwl_positions[k],
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
|
||||
# add pins for RWBL and RWBR
|
||||
rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
|
||||
layer="metal2",
|
||||
offset=self.rwbl_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.bitline_offset - 0.5*self.m2_width
|
||||
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
|
||||
layer="metal2",
|
||||
offset=self.rwbr_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
# update furthest left and right transistor edges
|
||||
self.left_building_edge = left_readwrite_transistor_xpos
|
||||
self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width
|
||||
|
||||
def create_write_ports(self):
|
||||
"""
|
||||
Creates write ports in the bit cell. A differential pair of transistors can write only.
|
||||
A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor.
|
||||
The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q).
|
||||
In a write operation, driving WBL high or low sets the value of the cell.
|
||||
This is a differential design, so each write port has a mirrored port that connects WBR to Q_bar.
|
||||
"""
|
||||
# define write transistor variables as empty arrays based on the number of write ports
|
||||
self.write_nmos_left = [None] * self.num_w_ports
|
||||
self.write_nmos_right = [None] * self.num_w_ports
|
||||
|
||||
# iterate over the number of write ports
|
||||
for k in range(0,self.num_w_ports):
|
||||
# add write transistors
|
||||
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
|
||||
mod=self.write_nmos)
|
||||
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"])
|
||||
|
||||
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
|
||||
mod=self.write_nmos)
|
||||
self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"])
|
||||
|
||||
def place_write_ports(self):
|
||||
""" Places write ports in the bit cell """
|
||||
# define write transistor variables as empty arrays based on the number of write ports
|
||||
self.wwl_positions = [None] * self.num_w_ports
|
||||
self.wbl_positions = [None] * self.num_w_ports
|
||||
self.wbr_positions = [None] * self.num_w_ports
|
||||
|
||||
# iterate over the number of write ports
|
||||
for k in range(0,self.num_w_ports):
|
||||
# Add transistors
|
||||
# calculate write transistor offsets
|
||||
left_write_transistor_xpos = self.left_building_edge \
|
||||
- (k+1)*self.write_port_spacing \
|
||||
- (k+1)*self.write_nmos.active_width
|
||||
|
||||
right_write_transistor_xpos = self.right_building_edge \
|
||||
+ (k+1)*self.write_port_spacing \
|
||||
+ k*self.write_nmos.active_width
|
||||
|
||||
# add write transistors
|
||||
self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos])
|
||||
|
||||
self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos])
|
||||
|
||||
# add pin for WWL
|
||||
wwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing
|
||||
self.wwl_positions[k] = vector(0, wwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
|
||||
layer="metal1",
|
||||
offset=self.wwl_positions[k],
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
|
||||
# add pins for WBL and WBR
|
||||
wbl_xpos = left_write_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
|
||||
layer="metal2",
|
||||
offset=self.wbl_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset - 0.5*self.m2_width
|
||||
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.w_br_names[k],
|
||||
layer="metal2",
|
||||
offset=self.wbr_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
# update furthest left and right transistor edges
|
||||
self.left_building_edge = left_write_transistor_xpos
|
||||
self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width
|
||||
|
||||
def create_read_ports(self):
|
||||
"""
|
||||
Creates read ports in the bit cell. A differential pair of ports can read only.
|
||||
Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor".
|
||||
The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source).
|
||||
The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain).
|
||||
A read is enabled by setting a Read-Rowline (RWL) high, subsequently turning on the read transistor.
|
||||
The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor
|
||||
is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read
|
||||
using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q.
|
||||
"""
|
||||
|
||||
# define read transistor variables as empty arrays based on the number of read ports
|
||||
self.read_nmos_left = [None] * self.num_r_ports
|
||||
self.read_nmos_right = [None] * self.num_r_ports
|
||||
self.read_access_nmos_left = [None] * self.num_r_ports
|
||||
self.read_access_nmos_right = [None] * self.num_r_ports
|
||||
|
||||
# iterate over the number of read ports
|
||||
for k in range(0,self.num_r_ports):
|
||||
# add read-access transistors
|
||||
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
|
||||
mod=self.read_nmos)
|
||||
self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"])
|
||||
|
||||
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
|
||||
mod=self.read_nmos)
|
||||
self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"])
|
||||
|
||||
# add read transistors
|
||||
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k),
|
||||
mod=self.read_nmos)
|
||||
self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"])
|
||||
|
||||
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k),
|
||||
mod=self.read_nmos)
|
||||
self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"])
|
||||
|
||||
def place_read_ports(self):
|
||||
""" Places the read ports in the bit cell """
|
||||
# define read transistor variables as empty arrays based on the number of read ports
|
||||
self.rwl_positions = [None] * self.num_r_ports
|
||||
self.rbl_positions = [None] * self.num_r_ports
|
||||
self.rbr_positions = [None] * self.num_r_ports
|
||||
|
||||
# calculate offset to overlap the drain of the read-access transistor with the source of the read transistor
|
||||
overlap_offset = self.read_nmos.get_pin("D").cx() - self.read_nmos.get_pin("S").cx()
|
||||
|
||||
# iterate over the number of read ports
|
||||
for k in range(0,self.num_r_ports):
|
||||
# calculate transistor offsets
|
||||
left_read_transistor_xpos = self.left_building_edge \
|
||||
- (k+1)*self.read_port_spacing \
|
||||
- (k+1)*self.read_port_width
|
||||
|
||||
right_read_transistor_xpos = self.right_building_edge \
|
||||
+ (k+1)*self.read_port_spacing \
|
||||
+ k*self.read_port_width
|
||||
|
||||
# add read-access transistors
|
||||
self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos])
|
||||
|
||||
self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos])
|
||||
|
||||
# add read transistors
|
||||
self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos])
|
||||
|
||||
self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos])
|
||||
|
||||
# add pin for RWL
|
||||
rwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing
|
||||
self.rwl_positions[k] = vector(0, rwl_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
|
||||
layer="metal1",
|
||||
offset=self.rwl_positions[k],
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
|
||||
# add pins for RBL and RBR
|
||||
rbl_xpos = left_read_transistor_xpos - self.bitline_offset + 0.5*self.m2_width
|
||||
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
|
||||
layer="metal2",
|
||||
offset=self.rbl_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset - 0.5*self.m2_width
|
||||
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
|
||||
self.add_layout_pin_rect_center(text=self.r_br_names[k],
|
||||
layer="metal2",
|
||||
offset=self.rbr_positions[k],
|
||||
width=drc["minwidth_metal2"],
|
||||
height=self.height)
|
||||
|
||||
def route_wordlines(self):
|
||||
""" Routes gate of transistors to their respective wordlines """
|
||||
port_transistors = []
|
||||
for k in range(self.num_rw_ports):
|
||||
port_transistors.append(self.readwrite_nmos_left[k])
|
||||
port_transistors.append(self.readwrite_nmos_right[k])
|
||||
for k in range(self.num_w_ports):
|
||||
port_transistors.append(self.write_nmos_left[k])
|
||||
port_transistors.append(self.write_nmos_right[k])
|
||||
for k in range(self.num_r_ports):
|
||||
port_transistors.append(self.read_nmos_left[k])
|
||||
port_transistors.append(self.read_nmos_right[k])
|
||||
|
||||
wl_positions = []
|
||||
for k in range(self.num_rw_ports):
|
||||
wl_positions.append(self.rwwl_positions[k])
|
||||
wl_positions.append(self.rwwl_positions[k])
|
||||
for k in range(self.num_w_ports):
|
||||
wl_positions.append(self.wwl_positions[k])
|
||||
wl_positions.append(self.wwl_positions[k])
|
||||
for k in range(self.num_r_ports):
|
||||
wl_positions.append(self.rwl_positions[k])
|
||||
wl_positions.append(self.rwl_positions[k])
|
||||
|
||||
for k in range(2*self.total_ports):
|
||||
gate_offset = port_transistors[k].get_pin("G").bc()
|
||||
port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active)
|
||||
wl_contact_offset = vector(gate_offset.x, wl_positions[k].y)
|
||||
|
||||
# first transistor on either side of the cross coupled inverters does not need to route to wordline on metal2
|
||||
if (k == 0) or (k == 1):
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||
self.add_path("metal1", [port_contact_offset, wl_contact_offset])
|
||||
|
||||
else:
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=port_contact_offset)
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=wl_contact_offset,
|
||||
rotate=90)
|
||||
|
||||
self.add_path("poly", [gate_offset, port_contact_offset])
|
||||
self.add_path("metal2", [port_contact_offset, wl_contact_offset])
|
||||
|
||||
def route_bitlines(self):
|
||||
""" Routes read/write transistors to their respective bitlines """
|
||||
left_port_transistors = []
|
||||
right_port_transistors = []
|
||||
for k in range(self.num_rw_ports):
|
||||
left_port_transistors.append(self.readwrite_nmos_left[k])
|
||||
right_port_transistors.append(self.readwrite_nmos_right[k])
|
||||
for k in range(self.num_w_ports):
|
||||
left_port_transistors.append(self.write_nmos_left[k])
|
||||
right_port_transistors.append(self.write_nmos_right[k])
|
||||
for k in range(self.num_r_ports):
|
||||
left_port_transistors.append(self.read_nmos_left[k])
|
||||
right_port_transistors.append(self.read_nmos_right[k])
|
||||
|
||||
bl_positions = []
|
||||
br_positions = []
|
||||
for k in range(self.num_rw_ports):
|
||||
bl_positions.append(self.rwbl_positions[k])
|
||||
br_positions.append(self.rwbr_positions[k])
|
||||
for k in range(self.num_w_ports):
|
||||
bl_positions.append(self.wbl_positions[k])
|
||||
br_positions.append(self.wbr_positions[k])
|
||||
for k in range(self.num_r_ports):
|
||||
bl_positions.append(self.rbl_positions[k])
|
||||
br_positions.append(self.rbr_positions[k])
|
||||
|
||||
for k in range(self.total_ports):
|
||||
port_contact_offest = left_port_transistors[k].get_pin("S").center()
|
||||
bl_offset = vector(bl_positions[k].x, port_contact_offest.y)
|
||||
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=port_contact_offest)
|
||||
|
||||
self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height)
|
||||
|
||||
for k in range(self.total_ports):
|
||||
port_contact_offest = right_port_transistors[k].get_pin("D").center()
|
||||
br_offset = vector(br_positions[k].x, port_contact_offest.y)
|
||||
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=port_contact_offest)
|
||||
|
||||
self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height)
|
||||
|
||||
def route_supply(self):
|
||||
""" Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """
|
||||
# route inverter nmos and read-access nmos to gnd
|
||||
nmos_contact_positions = []
|
||||
nmos_contact_positions.append(self.inverter_nmos_left.get_pin("S").center())
|
||||
nmos_contact_positions.append(self.inverter_nmos_right.get_pin("D").center())
|
||||
for k in range(self.num_r_ports):
|
||||
nmos_contact_positions.append(self.read_access_nmos_left[k].get_pin("D").center())
|
||||
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
|
||||
|
||||
for position in nmos_contact_positions:
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=position)
|
||||
|
||||
if position.x > 0:
|
||||
contact_correct = 0.5*contact.m1m2.height
|
||||
else:
|
||||
contact_correct = -0.5*contact.m1m2.height
|
||||
supply_offset = vector(position.x + contact_correct, self.gnd_position.y)
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=supply_offset,
|
||||
rotate=90)
|
||||
|
||||
self.add_path("metal2", [position, supply_offset])
|
||||
|
||||
# route inverter pmos to vdd
|
||||
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y)
|
||||
self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
||||
|
||||
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y)
|
||||
self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
||||
|
||||
def route_readwrite_access(self):
|
||||
""" Routes read/write transistors to the storage component of the bitcell """
|
||||
for k in range(self.num_rw_ports):
|
||||
mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
|
||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, Q_pos])
|
||||
|
||||
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
|
||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, Q_bar_pos])
|
||||
|
||||
def route_write_access(self):
|
||||
""" Routes read/write transistors to the storage component of the bitcell """
|
||||
for k in range(self.num_w_ports):
|
||||
mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
|
||||
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1", [self.write_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, Q_pos])
|
||||
|
||||
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
|
||||
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
|
||||
self.add_path("metal1", [self.write_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, Q_bar_pos])
|
||||
|
||||
def route_read_access(self):
|
||||
""" Routes read access transistors to the storage component of the bitcell """
|
||||
# add poly to metal1 contacts for gates of the inverters
|
||||
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_upper_ypos)
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=left_storage_contact,
|
||||
rotate=90)
|
||||
|
||||
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_upper_ypos)
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=right_storage_contact,
|
||||
rotate=90)
|
||||
|
||||
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos)
|
||||
self.add_path("poly", [left_storage_contact, inverter_gate_offset_left])
|
||||
|
||||
inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_upper_ypos)
|
||||
self.add_path("poly", [right_storage_contact, inverter_gate_offset_right])
|
||||
|
||||
# add poly to metal1 contacts for gates of read-access transistors
|
||||
# route from read-access contacts to inverter contacts on metal1
|
||||
for k in range(self.num_r_ports):
|
||||
port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
|
||||
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset])
|
||||
|
||||
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
|
||||
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, left_storage_contact])
|
||||
|
||||
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
|
||||
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
offset=port_contact_offset)
|
||||
|
||||
self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset])
|
||||
|
||||
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
|
||||
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
|
||||
self.add_path("metal1", [mid, right_storage_contact])
|
||||
|
||||
def extend_well(self):
|
||||
"""
|
||||
Connects wells between ptx modules and places well contacts"""
|
||||
# extend pwell to encompass entire nmos region of the cell up to the height of the tallest nmos transistor
|
||||
max_nmos_well_height = max(self.inverter_nmos.cell_well_height,
|
||||
self.readwrite_nmos.cell_well_height,
|
||||
self.write_nmos.cell_well_height,
|
||||
self.read_nmos.cell_well_height)
|
||||
well_height = max_nmos_well_height + self.port_ypos - self.well_enclose_active - self.gnd_position.y
|
||||
offset = vector(self.leftmost_xpos, self.botmost_ypos)
|
||||
self.add_rect(layer="pwell",
|
||||
offset=offset,
|
||||
width=self.width,
|
||||
height=well_height)
|
||||
|
||||
# extend nwell to encompass inverter_pmos
|
||||
# calculate offset of the left pmos well
|
||||
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) - drc["well_enclosure_active"]
|
||||
inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"]
|
||||
|
||||
# calculate width of the two combined nwells
|
||||
# calculate height to encompass nimplant connected to vdd
|
||||
well_width = 2*(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) + 2*drc["well_enclosure_active"]
|
||||
well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
|
||||
|
||||
offset = [inverter_well_xpos,inverter_well_ypos]
|
||||
self.add_rect(layer="nwell",
|
||||
offset=offset,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
|
||||
# add well contacts
|
||||
# connect pimplants to gnd
|
||||
offset = vector(0, self.gnd_position.y)
|
||||
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||
offset=offset,
|
||||
rotate=90,
|
||||
implant_type="p",
|
||||
well_type="p")
|
||||
|
||||
# connect nimplants to vdd
|
||||
offset = vector(0, self.vdd_position.y)
|
||||
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||
offset=offset,
|
||||
rotate=90,
|
||||
implant_type="n",
|
||||
well_type="n")
|
||||
|
||||
def list_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
|
||||
bitcell_pins = []
|
||||
for port in range(self.total_ports):
|
||||
bitcell_pins.append("bl{0}_{1}".format(port,col))
|
||||
bitcell_pins.append("br{0}_{1}".format(port,col))
|
||||
for port in range(self.total_ports):
|
||||
bitcell_pins.append("wl{0}_{1}".format(port,row))
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
return bitcell_pins
|
||||
|
||||
def list_all_wl_names(self):
|
||||
""" Creates a list of all wordline pin names """
|
||||
wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names
|
||||
return wordline_names
|
||||
|
||||
def list_all_bitline_names(self):
|
||||
""" Creates a list of all bitline pin names (both bl and br) """
|
||||
bitline_pins = []
|
||||
for port in range(self.total_ports):
|
||||
bitline_pins.append("bl{0}".format(port))
|
||||
bitline_pins.append("br{0}".format(port))
|
||||
return bitline_pins
|
||||
|
||||
def list_all_bl_names(self):
|
||||
""" Creates a list of all bl pins names """
|
||||
bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names
|
||||
return bl_pins
|
||||
|
||||
def list_all_br_names(self):
|
||||
""" Creates a list of all br pins names """
|
||||
br_pins = self.rw_br_names + self.w_br_names + self.r_br_names
|
||||
return br_pins
|
||||
|
||||
def route_rbc_short(self):
|
||||
""" route the short from Q_bar to gnd necessary for the replica bitcell """
|
||||
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
|
||||
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
||||
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|
||||
|
||||
def analytical_delay(self, slew, load=0, swing = 0.5):
|
||||
#FIXME: Delay copied exactly over from bitcell
|
||||
from tech import spice
|
||||
r = spice["min_tx_r"]*3
|
||||
c_para = spice["min_tx_drain_c"]
|
||||
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
|
||||
return result
|
||||
|
||||
def analytical_power(self, proc, vdd, temp, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
leakage = spice["bitcell_leakage"]
|
||||
dynamic = 0 #temporary
|
||||
total_power = self.return_power(dynamic, leakage)
|
||||
return total_power
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#pbitcell uses the different sizing for the port access tx's. Not accounted for in this model.
|
||||
access_tx_cin = self.readwrite_nmos.get_cin()
|
||||
return 2*access_tx_cin
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer
|
||||
from tech import GDS,layer,drc,parameter
|
||||
|
||||
class replica_bitcell(design.design):
|
||||
"""
|
||||
|
|
@ -12,7 +12,7 @@ class replica_bitcell(design.design):
|
|||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||
|
||||
def __init__(self):
|
||||
design.design.__init__(self, "replica_cell_6t")
|
||||
|
|
@ -21,3 +21,10 @@ class replica_bitcell(design.design):
|
|||
self.width = replica_bitcell.width
|
||||
self.height = replica_bitcell.height
|
||||
self.pin_map = replica_bitcell.pin_map
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#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 related gates and dividing by the minimum width.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer,drc,parameter
|
||||
|
||||
class replica_bitcell_1rw_1r(design.design):
|
||||
"""
|
||||
A single bit cell which is forced to store a 0.
|
||||
This module implements the single memory cell used in the design. It
|
||||
is a hand-made cell, so the layout and netlist should be available in
|
||||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
|
||||
|
||||
def __init__(self):
|
||||
design.design.__init__(self, "replica_cell_1rw_1r")
|
||||
debug.info(2, "Create replica bitcell 1rw+1r object")
|
||||
|
||||
self.width = replica_bitcell_1rw_1r.width
|
||||
self.height = replica_bitcell_1rw_1r.height
|
||||
self.pin_map = replica_bitcell_1rw_1r.pin_map
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#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 related gates and dividing by the minimum width.
|
||||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import debug
|
||||
import design
|
||||
from tech import drc, spice,parameter
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from pbitcell import pbitcell
|
||||
|
||||
class replica_pbitcell(design.design):
|
||||
"""
|
||||
Creates a replica bitcell using pbitcell
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.num_rw_ports = OPTS.num_rw_ports
|
||||
self.num_w_ports = OPTS.num_w_ports
|
||||
self.num_r_ports = OPTS.num_r_ports
|
||||
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
|
||||
|
||||
design.design.__init__(self, "replica_pbitcell")
|
||||
debug.info(1, "create a replica bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
|
||||
self.num_w_ports,
|
||||
self.num_r_ports))
|
||||
|
||||
self.create_netlist()
|
||||
self.create_layout()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_modules()
|
||||
|
||||
def create_layout(self):
|
||||
self.place_pbitcell()
|
||||
self.route_rbc_connections()
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
for port in range(self.total_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
|
||||
for port in range(self.total_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def add_modules(self):
|
||||
self.prbc = pbitcell(replica_bitcell=True)
|
||||
self.add_mod(self.prbc)
|
||||
|
||||
self.height = self.prbc.height
|
||||
self.width = self.prbc.width
|
||||
|
||||
def create_modules(self):
|
||||
self.prbc_inst = self.add_inst(name="pbitcell",
|
||||
mod=self.prbc)
|
||||
|
||||
temp = []
|
||||
for port in range(self.total_ports):
|
||||
temp.append("bl{}".format(port))
|
||||
temp.append("br{}".format(port))
|
||||
for port in range(self.total_ports):
|
||||
temp.append("wl{}".format(port))
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
def place_pbitcell(self):
|
||||
offset = [0,0]
|
||||
self.prbc_inst.place(offset=offset)
|
||||
|
||||
def route_rbc_connections(self):
|
||||
for port in range(self.total_ports):
|
||||
self.copy_layout_pin(self.prbc_inst, "bl{}".format(port))
|
||||
self.copy_layout_pin(self.prbc_inst, "br{}".format(port))
|
||||
for port in range(self.total_ports):
|
||||
self.copy_layout_pin(self.prbc_inst, "wl{}".format(port))
|
||||
self.copy_layout_pin(self.prbc_inst, "vdd")
|
||||
self.copy_layout_pin(self.prbc_inst, "gnd")
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#This module is made using a pbitcell. Get the cin from that module
|
||||
return self.prbc.get_wl_cin()
|
||||
|
|
@ -5,7 +5,10 @@ from globals import OPTS,find_exe,get_tool
|
|||
from .lib import *
|
||||
from .delay import *
|
||||
from .setup_hold import *
|
||||
|
||||
from .functional import *
|
||||
from .worst_case import *
|
||||
from .simulation import *
|
||||
from .bitline_delay import *
|
||||
|
||||
debug.info(1,"Initializing characterizer...")
|
||||
OPTS.spice_exe = ""
|
||||
|
|
@ -15,10 +18,10 @@ if not OPTS.analytical_delay:
|
|||
|
||||
if OPTS.spice_name != "":
|
||||
OPTS.spice_exe=find_exe(OPTS.spice_name)
|
||||
if OPTS.spice_exe=="":
|
||||
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
|
||||
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1)
|
||||
else:
|
||||
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["xa", "hspice", "ngspice", "ngspice.exe"])
|
||||
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["hspice", "ngspice", "ngspice.exe", "xa"])
|
||||
|
||||
# set the input dir for spice files if using ngspice
|
||||
if OPTS.spice_name == "ngspice":
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
import sys,re,shutil
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from .delay import delay
|
||||
|
||||
class bitline_delay(delay):
|
||||
"""Functions to test for the worst case delay in a target SRAM
|
||||
|
||||
The current worst case determines a feasible period for the SRAM then tests
|
||||
several bits and record the delay and differences between the bits.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
delay.__init__(self,sram,spfile,corner)
|
||||
self.period = tech.spice["feasible_period"]
|
||||
self.is_bitline_measure = True
|
||||
|
||||
def create_measurement_names(self):
|
||||
"""Create measurement names. The names themselves currently define the type of measurement"""
|
||||
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
|
||||
self.bitline_meas_names = ["bl_volt", "br_volt"]
|
||||
|
||||
def write_delay_measures(self):
|
||||
"""
|
||||
Write the measure statements to quantify the bitline voltage at sense amp enable 50%.
|
||||
"""
|
||||
self.sf.write("\n* Measure statements for delay and power\n")
|
||||
|
||||
# Output some comments to aid where cycles start and
|
||||
for comment in self.cycle_comments:
|
||||
self.sf.write("* {}\n".format(comment))
|
||||
|
||||
for read_port in self.targ_read_ports:
|
||||
self.write_bitline_measures_read_port(read_port)
|
||||
|
||||
def write_bitline_measures_read_port(self, port):
|
||||
"""
|
||||
Write the measure statements to quantify the delay and power results for a read port.
|
||||
"""
|
||||
# add measure statements for delays/slews
|
||||
measure_bitline = self.get_data_bit_column_number(self.probe_address, self.probe_data)
|
||||
debug.info(2, "Measuring bitline column={}".format(measure_bitline))
|
||||
for port in self.targ_read_ports:
|
||||
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
|
||||
bitline_port = ""
|
||||
else:
|
||||
bitline_port = str(port)
|
||||
|
||||
sen_name = "Xsram.s_en{}".format(port)
|
||||
bl_name = "Xsram.Xbank0.bl{}_{}".format(bitline_port, measure_bitline)
|
||||
br_name = "Xsram.Xbank0.br{}_{}".format(bitline_port, measure_bitline)
|
||||
self.stim.gen_meas_find_voltage("bl_volt", sen_name, bl_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
|
||||
self.stim.gen_meas_find_voltage("br_volt", sen_name, br_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
|
||||
|
||||
def gen_test_cycles_one_port(self, read_port, write_port):
|
||||
"""Sets a list of key time-points [ns] of the waveform (each rising edge)
|
||||
of the cycles to do a timing evaluation of a single port """
|
||||
|
||||
# Create the inverse address for a scratch address
|
||||
inverse_address = self.calculate_inverse_address()
|
||||
|
||||
# For now, ignore data patterns and write ones or zeros
|
||||
data_ones = "1"*self.word_size
|
||||
data_zeros = "0"*self.word_size
|
||||
|
||||
if self.t_current == 0:
|
||||
self.add_noop_all_ports("Idle cycle (no positive clock edge)",
|
||||
inverse_address, data_zeros)
|
||||
|
||||
self.add_write("W data 1 address {}".format(inverse_address),
|
||||
inverse_address,data_ones,write_port)
|
||||
|
||||
self.add_write("W data 0 address {} to write value".format(self.probe_address),
|
||||
self.probe_address,data_zeros,write_port)
|
||||
self.measure_cycles[write_port]["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,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1
|
||||
|
||||
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
|
||||
self.probe_address,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
|
||||
def get_data_bit_column_number(self, probe_address, probe_data):
|
||||
"""Calculates bitline column number of data bit under test using bit position and mux size"""
|
||||
if self.sram.col_addr_size>0:
|
||||
col_address = int(probe_address[0:self.sram.col_addr_size],2)
|
||||
else:
|
||||
col_address = 0
|
||||
bl_column = int(self.sram.words_per_row*probe_data + col_address)
|
||||
return bl_column
|
||||
|
||||
def run_delay_simulation(self):
|
||||
"""
|
||||
This tries to simulate a period and checks if the result works. If
|
||||
so, it returns True and the delays, slews, and powers. It
|
||||
works on the trimmed netlist by default, so powers do not
|
||||
include leakage of all cells.
|
||||
"""
|
||||
#Sanity Check
|
||||
debug.check(self.period > 0, "Target simulation period non-positive")
|
||||
|
||||
result = [{} for i in self.all_ports]
|
||||
# Checking from not data_value to data_value
|
||||
self.write_delay_stimulus()
|
||||
|
||||
self.stim.run_sim() #running sim prodoces spice output file.
|
||||
|
||||
for port in self.targ_read_ports:
|
||||
bitlines_meas_vals = {}
|
||||
for mname in self.bitline_meas_names:
|
||||
bitlines_meas_vals[mname] = parse_spice_list("timing", mname)
|
||||
#Check that power parsing worked.
|
||||
for name, val in bitlines_meas_vals.items():
|
||||
if type(val)!=float:
|
||||
debug.error("Failed to Parse Bitline Values:\n\t\t{0}".format(bitlines_meas_vals),1) #Printing the entire dict looks bad.
|
||||
result[port].update(bitlines_meas_vals)
|
||||
|
||||
|
||||
# The delay is from the negative edge for our SRAM
|
||||
return (True,result)
|
||||
|
||||
def analyze(self, probe_address, probe_data, slews, loads):
|
||||
"""Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
|
||||
read_port = self.read_ports[0] #only test the first read port
|
||||
bitline_swings = {}
|
||||
self.targ_read_ports = [read_port]
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
|
||||
(success, results)=self.run_delay_simulation()
|
||||
debug.check(success, "Bitline Failed: period {}".format(self.period))
|
||||
for mname in self.bitline_meas_names:
|
||||
bitline_swings[mname] = results[read_port][mname]
|
||||
debug.info(1,"Bitline values (bl/br): {}".format(bitline_swings))
|
||||
return bitline_swings
|
||||
|
||||
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ from globals import OPTS
|
|||
|
||||
def relative_compare(value1,value2,error_tolerance=0.001):
|
||||
""" This is used to compare relative values for convergence. """
|
||||
return (abs(value1 - value2) / max(value1,value2) <= error_tolerance)
|
||||
return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance)
|
||||
|
||||
|
||||
def parse_spice_list(filename, key):
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,286 @@
|
|||
import sys,re,shutil
|
||||
from design import design
|
||||
import debug
|
||||
import math
|
||||
import tech
|
||||
import random
|
||||
from .stimuli import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
|
||||
from .simulation import simulation
|
||||
from .delay import delay
|
||||
|
||||
class functional(simulation):
|
||||
"""
|
||||
Functions to write random data values to a random address then read them back and check
|
||||
for successful SRAM operation.
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
simulation.__init__(self, sram, spfile, corner)
|
||||
|
||||
# Seed the characterizer with a constant seed for unit tests
|
||||
if OPTS.is_unit_test:
|
||||
random.seed(12345)
|
||||
|
||||
self.set_corner(corner)
|
||||
self.set_spice_constants()
|
||||
#self.set_feasible_period(sram, spfile, corner)
|
||||
self.set_stimulus_variables()
|
||||
self.create_signal_names()
|
||||
|
||||
|
||||
# Number of checks can be changed
|
||||
self.num_cycles = 2
|
||||
self.stored_words = {}
|
||||
self.write_check = []
|
||||
self.read_check = []
|
||||
|
||||
def run(self, feasible_period=None):
|
||||
if feasible_period: #period defaults to tech.py feasible period otherwise.
|
||||
self.period = feasible_period
|
||||
# Generate a random sequence of reads and writes
|
||||
self.write_random_memory_sequence()
|
||||
|
||||
# Run SPICE simulation
|
||||
self.write_functional_stimulus()
|
||||
self.stim.run_sim()
|
||||
|
||||
# read DOUT values from SPICE simulation. If the values do not fall within the noise margins, return the error.
|
||||
(success, error) = self.read_stim_results()
|
||||
if not success:
|
||||
return (0, error)
|
||||
|
||||
# Check read values with written values. If the values do not match, return an error.
|
||||
return self.check_stim_results()
|
||||
|
||||
def write_random_memory_sequence(self):
|
||||
rw_ops = ["noop", "write", "read"]
|
||||
w_ops = ["noop", "write"]
|
||||
r_ops = ["noop", "read"]
|
||||
rw_read_din_data = "0"*self.word_size
|
||||
check = 0
|
||||
|
||||
# First cycle idle
|
||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
||||
|
||||
# Write at least once
|
||||
addr = self.gen_addr()
|
||||
word = self.gen_data()
|
||||
comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current)
|
||||
self.add_write(comment, addr, word, 0)
|
||||
self.stored_words[addr] = word
|
||||
|
||||
# Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously.
|
||||
# This will test the viablilty of the transistor sizing in the bitcell.
|
||||
for port in self.all_ports:
|
||||
if port in self.write_ports:
|
||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||
else:
|
||||
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
||||
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
||||
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||
check += 1
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
||||
# Perform a random sequence of writes and reads on random ports, using random addresses and random words
|
||||
for i in range(self.num_cycles):
|
||||
w_addrs = []
|
||||
for port in self.all_ports:
|
||||
if port in self.readwrite_ports:
|
||||
op = random.choice(rw_ops)
|
||||
elif port in self.write_ports:
|
||||
op = random.choice(w_ops)
|
||||
else:
|
||||
op = random.choice(r_ops)
|
||||
|
||||
if op == "noop":
|
||||
addr = "0"*self.addr_size
|
||||
word = "0"*self.word_size
|
||||
self.add_noop_one_port(addr, word, port)
|
||||
elif op == "write":
|
||||
addr = self.gen_addr()
|
||||
word = self.gen_data()
|
||||
# two ports cannot write to the same address
|
||||
if addr in w_addrs:
|
||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||
else:
|
||||
comment = self.gen_cycle_comment("write", word, addr, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word, port)
|
||||
self.stored_words[addr] = word
|
||||
w_addrs.append(addr)
|
||||
else:
|
||||
(addr,word) = random.choice(list(self.stored_words.items()))
|
||||
# cannot read from an address that is currently being written to
|
||||
if addr in w_addrs:
|
||||
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
|
||||
else:
|
||||
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
|
||||
self.add_read_one_port(comment, addr, rw_read_din_data, port)
|
||||
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
|
||||
check += 1
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
||||
# Last cycle idle needed to correctly measure the value on the second to last clock edge
|
||||
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
|
||||
|
||||
def read_stim_results(self):
|
||||
# Extrat DOUT values from spice timing.lis
|
||||
for (word, dout_port, eo_period, check) in self.write_check:
|
||||
sp_read_value = ""
|
||||
for bit in range(self.word_size):
|
||||
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
|
||||
if value > self.v_high:
|
||||
sp_read_value = "1" + sp_read_value
|
||||
elif value < self.v_low:
|
||||
sp_read_value = "0" + sp_read_value
|
||||
else:
|
||||
error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port,
|
||||
bit,
|
||||
value,
|
||||
eo_period,
|
||||
self.v_low,
|
||||
self.v_high)
|
||||
return (0, error)
|
||||
|
||||
self.read_check.append([sp_read_value, dout_port, eo_period, check])
|
||||
return (1, "SUCCESS")
|
||||
|
||||
def check_stim_results(self):
|
||||
for i in range(len(self.write_check)):
|
||||
if self.write_check[i][0] != self.read_check[i][0]:
|
||||
error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1],
|
||||
self.read_check[i][0],
|
||||
self.write_check[i][0],
|
||||
int((self.read_check[i][2]-self.period)/self.period),
|
||||
self.read_check[i][2])
|
||||
return(0, error)
|
||||
return(1, "SUCCESS")
|
||||
|
||||
def gen_data(self):
|
||||
""" Generates a random word to write. """
|
||||
rand = random.randint(0,(2**self.word_size)-1)
|
||||
data_bits = self.convert_to_bin(rand,False)
|
||||
return data_bits
|
||||
|
||||
def gen_addr(self):
|
||||
""" Generates a random address value to write to. """
|
||||
rand = random.randint(0,(2**self.addr_size)-1)
|
||||
addr_bits = self.convert_to_bin(rand,True)
|
||||
return addr_bits
|
||||
|
||||
def get_data(self):
|
||||
""" Gets an available address and corresponding word. """
|
||||
# Currently unused but may need later depending on how the functional test develops
|
||||
addr = random.choice(self.stored_words.keys())
|
||||
word = self.stored_words[addr]
|
||||
return (addr,word)
|
||||
|
||||
def convert_to_bin(self,value,is_addr):
|
||||
""" Converts addr & word to usable binary values. """
|
||||
new_value = str.replace(bin(value),"0b","")
|
||||
if(is_addr):
|
||||
expected_value = self.addr_size
|
||||
else:
|
||||
expected_value = self.word_size
|
||||
for i in range (expected_value - len(new_value)):
|
||||
new_value = "0" + new_value
|
||||
|
||||
#print("Binary Conversion: {} to {}".format(value, new_value))
|
||||
return new_value
|
||||
|
||||
def create_signal_names(self):
|
||||
self.addr_name = "A"
|
||||
self.din_name = "DIN"
|
||||
self.dout_name = "DOUT"
|
||||
|
||||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
||||
self.sf = open(temp_stim,"w")
|
||||
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf,self.corner)
|
||||
|
||||
#Write include statements
|
||||
self.sram_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
shutil.copy(self.sp_file, self.sram_sp_file)
|
||||
self.stim.write_include(self.sram_sp_file)
|
||||
|
||||
#Write Vdd/Gnd statements
|
||||
self.sf.write("\n* Global Power Supplies\n")
|
||||
self.stim.write_supply()
|
||||
|
||||
#Instantiate the SRAM
|
||||
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||
self.stim.inst_sram(sram=self.sram,
|
||||
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||
port_info=(len(self.all_ports), self.write_ports, self.read_ports),
|
||||
abits=self.addr_size,
|
||||
dbits=self.word_size,
|
||||
sram_name=self.name)
|
||||
|
||||
# Add load capacitance to each of the read ports
|
||||
self.sf.write("\n* SRAM output loads\n")
|
||||
for port in self.read_ports:
|
||||
for bit in range(self.word_size):
|
||||
sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit)
|
||||
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load))
|
||||
|
||||
# Write debug comments to stim file
|
||||
self.sf.write("\n\n * Sequence of operations\n")
|
||||
for comment in self.fn_cycle_comments:
|
||||
self.sf.write("*{}\n".format(comment))
|
||||
|
||||
# Generate data input bits
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
for port in self.write_ports:
|
||||
for bit in range(self.word_size):
|
||||
sig_name="{0}{1}_{2} ".format(self.din_name, port, bit)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
||||
# Generate address bits
|
||||
for port in self.all_ports:
|
||||
for bit in range(self.addr_size):
|
||||
sig_name="{0}{1}_{2} ".format(self.addr_name, port, bit)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
||||
# Generate control signals
|
||||
self.sf.write("\n * Generation of control signals\n")
|
||||
for port in self.all_ports:
|
||||
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times , self.csb_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
for port in self.readwrite_ports:
|
||||
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times , self.web_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
# Generate CLK signals
|
||||
for port in self.all_ports:
|
||||
self.stim.gen_pulse(sig_name="{0}{1}".format(tech.spice["clk"], port),
|
||||
v1=self.gnd_voltage,
|
||||
v2=self.vdd_voltage,
|
||||
offset=self.period,
|
||||
period=self.period,
|
||||
t_rise=self.slew,
|
||||
t_fall=self.slew)
|
||||
|
||||
# Generate DOUT value measurements
|
||||
self.sf.write("\n * Generation of dout measurements\n")
|
||||
for (word, dout_port, eo_period, check) in self.write_check:
|
||||
t_intital = eo_period - 0.01*self.period
|
||||
t_final = eo_period + 0.01*self.period
|
||||
for bit in range(self.word_size):
|
||||
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port,bit,check),
|
||||
dout="{0}_{1}".format(dout_port,bit),
|
||||
t_intital=t_intital,
|
||||
t_final=t_final)
|
||||
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
self.sf.close()
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import os,sys,re
|
||||
import debug
|
||||
import math
|
||||
import datetime
|
||||
from .setup_hold import *
|
||||
from .delay import *
|
||||
from .charutils import *
|
||||
|
|
@ -12,16 +13,12 @@ class lib:
|
|||
""" lib file generation."""
|
||||
|
||||
def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay):
|
||||
#Temporary Workaround to here to set num of ports. Crashes if set in config file.
|
||||
#OPTS.num_rw_ports = 2
|
||||
#OPTS.num_r_ports = 1
|
||||
#OPTS.num_w_ports = 1
|
||||
|
||||
self.out_dir = out_dir
|
||||
self.sram = sram
|
||||
self.sp_file = sp_file
|
||||
self.use_model = use_model
|
||||
self.gen_port_names() #copy and paste from delay.py, names are not final will likely be changed later.
|
||||
self.set_port_indices()
|
||||
|
||||
self.prepare_tables()
|
||||
|
||||
|
|
@ -29,26 +26,14 @@ class lib:
|
|||
|
||||
self.characterize_corners()
|
||||
|
||||
def gen_port_names(self):
|
||||
"""Generates the port names to be written to the lib file"""
|
||||
#This is basically a copy and paste of whats in delay.py as well. Something more efficient should be done here.
|
||||
self.write_ports = []
|
||||
self.read_ports = []
|
||||
self.total_port_num = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
#save a member variable to avoid accessing global. readwrite ports have different control signals.
|
||||
self.readwrite_port_num = OPTS.num_rw_ports
|
||||
|
||||
#Generate the port names. readwrite ports are required to be added first for this to work.
|
||||
for readwrite_port_num in range(OPTS.num_rw_ports):
|
||||
self.read_ports.append(readwrite_port_num)
|
||||
self.write_ports.append(readwrite_port_num)
|
||||
#This placement is intentional. It makes indexing input data easier. See self.data_values
|
||||
for read_port_num in range(OPTS.num_rw_ports, OPTS.num_r_ports):
|
||||
self.read_ports.append(read_port_num)
|
||||
for write_port_num in range(OPTS.num_rw_ports+OPTS.num_r_ports, OPTS.num_w_ports):
|
||||
self.write_ports.append(write_port_num)
|
||||
|
||||
def set_port_indices(self):
|
||||
"""Copies port information set in the SRAM instance"""
|
||||
self.total_port_num = len(self.sram.all_ports)
|
||||
self.all_ports = self.sram.all_ports
|
||||
self.readwrite_ports = self.sram.readwrite_ports
|
||||
self.read_ports = self.sram.read_ports
|
||||
self.write_ports = self.sram.write_ports
|
||||
|
||||
def prepare_tables(self):
|
||||
""" Determine the load/slews if they aren't specified in the config file. """
|
||||
# These are the parameters to determine the table sizes
|
||||
|
|
@ -100,7 +85,7 @@ class lib:
|
|||
debug.info(1,"Writing to {0}".format(lib_name))
|
||||
self.characterize()
|
||||
self.lib.close()
|
||||
|
||||
self.parse_info(self.corner,lib_name)
|
||||
def characterize(self):
|
||||
""" Characterize the current corner. """
|
||||
|
||||
|
|
@ -110,21 +95,21 @@ class lib:
|
|||
|
||||
self.write_header()
|
||||
|
||||
#Loop over all readwrite ports. This is debugging. Will change later.
|
||||
for port in range(self.total_port_num):
|
||||
#Loop over all ports.
|
||||
for port in self.all_ports:
|
||||
#set the read and write port as inputs.
|
||||
self.write_data_bus(port)
|
||||
self.write_addr_bus(port)
|
||||
self.write_control_pins(port) #need to split this into sram and port control signals
|
||||
|
||||
self.write_clk_timing_power()
|
||||
self.write_clk_timing_power(port)
|
||||
|
||||
self.write_footer()
|
||||
|
||||
|
||||
def write_footer(self):
|
||||
""" Write the footer """
|
||||
self.lib.write("}\n")
|
||||
self.lib.write(" }\n") #Closing brace for the cell
|
||||
self.lib.write("}\n") #Closing brace for the library
|
||||
|
||||
def write_header(self):
|
||||
""" Write the header information """
|
||||
|
|
@ -153,7 +138,7 @@ 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. This is subject to change once control signals finalized.
|
||||
#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)
|
||||
|
|
@ -161,7 +146,7 @@ class lib:
|
|||
# Leakage is included in dynamic when macro is enabled
|
||||
self.lib.write(" leakage_power () {\n")
|
||||
self.lib.write(" when : \"{0}\";\n".format(control_str))
|
||||
self.lib.write(" value : {};\n".format(self.char_results["leakage_power"]))
|
||||
self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"]))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" cell_leakage_power : {};\n".format(0))
|
||||
|
||||
|
|
@ -298,12 +283,12 @@ class lib:
|
|||
self.lib.write(" }\n\n")
|
||||
|
||||
|
||||
def write_FF_setuphold(self):
|
||||
def write_FF_setuphold(self, port):
|
||||
""" Adds Setup and Hold timing results"""
|
||||
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type : setup_rising; \n")
|
||||
self.lib.write(" related_pin : \"clk\"; \n")
|
||||
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
|
||||
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
||||
rounded_values = list(map(round_time,self.times["setup_times_LH"]))
|
||||
self.write_values(rounded_values,len(self.slews)," ")
|
||||
|
|
@ -315,7 +300,7 @@ class lib:
|
|||
self.lib.write(" }\n")
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type : hold_rising; \n")
|
||||
self.lib.write(" related_pin : \"clk\"; \n")
|
||||
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
|
||||
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
|
||||
rounded_values = list(map(round_time,self.times["hold_times_LH"]))
|
||||
self.write_values(rounded_values,len(self.slews)," ")
|
||||
|
|
@ -340,30 +325,29 @@ class lib:
|
|||
self.lib.write(" }\n")
|
||||
|
||||
|
||||
self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port))
|
||||
self.write_FF_setuphold()
|
||||
self.lib.write(" pin(DOUT{}){{\n".format(read_port))
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_sense : non_unate; \n")
|
||||
self.lib.write(" related_pin : \"clk\"; \n")
|
||||
self.lib.write(" timing_type : rising_edge; \n")
|
||||
self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port))
|
||||
self.lib.write(" timing_type : falling_edge; \n")
|
||||
self.lib.write(" cell_rise(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["delay_lh{0}".format(read_port)],len(self.loads)," ")
|
||||
self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # rise delay
|
||||
self.lib.write(" cell_fall(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["delay_hl{0}".format(read_port)],len(self.loads)," ")
|
||||
self.write_values(self.char_port_results[read_port]["delay_hl"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # fall delay
|
||||
self.lib.write(" rise_transition(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["slew_lh{0}".format(read_port)],len(self.loads)," ")
|
||||
self.write_values(self.char_port_results[read_port]["slew_lh"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # rise trans
|
||||
self.lib.write(" fall_transition(CELL_TABLE) {\n")
|
||||
self.write_values(self.char_results["slew_hl{0}".format(read_port)],len(self.loads)," ")
|
||||
self.write_values(self.char_port_results[read_port]["slew_hl"],len(self.loads)," ")
|
||||
self.lib.write(" }\n") # fall trans
|
||||
self.lib.write(" }\n") # timing
|
||||
self.lib.write(" }\n") # pin
|
||||
self.lib.write(" }\n\n") # bus
|
||||
|
||||
def write_data_bus_input(self, write_port):
|
||||
""" Adds data bus timing results."""
|
||||
""" Adds DIN data bus timing results."""
|
||||
|
||||
self.lib.write(" bus(DIN{0}){{\n".format(write_port))
|
||||
self.lib.write(" bus_type : DATA; \n")
|
||||
|
|
@ -372,9 +356,12 @@ class lib:
|
|||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
self.lib.write(" memory_write(){ \n")
|
||||
self.lib.write(" address : ADDR{0}; \n".format(write_port))
|
||||
self.lib.write(" clocked_on : clk; \n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" clocked_on : clk{0}; \n".format(write_port))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" pin(DIN{}){{\n".format(write_port))
|
||||
self.write_FF_setuphold(write_port)
|
||||
self.lib.write(" }\n") # pin
|
||||
self.lib.write(" }\n") #bus
|
||||
|
||||
def write_data_bus(self, port):
|
||||
""" Adds data bus timing results."""
|
||||
|
|
@ -391,10 +378,10 @@ class lib:
|
|||
self.lib.write(" direction : input; \n")
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
self.lib.write(" max_transition : {0};\n".format(self.slews[-1]))
|
||||
self.lib.write(" pin(ADDR{1}[{0}:0])".format(self.sram.addr_size - 1, port))
|
||||
self.lib.write(" pin(ADDR{})".format(port))
|
||||
self.lib.write("{\n")
|
||||
|
||||
self.write_FF_setuphold()
|
||||
self.write_FF_setuphold(port)
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
|
|
@ -403,7 +390,7 @@ class lib:
|
|||
""" Adds control pins timing results."""
|
||||
#The control pins are still to be determined. This is a placeholder for what could be.
|
||||
ctrl_pin_names = ["CSb{0}".format(port)]
|
||||
if port in self.write_ports and port in self.read_ports:
|
||||
if port in self.readwrite_ports:
|
||||
ctrl_pin_names.append("WEb{0}".format(port))
|
||||
|
||||
for i in ctrl_pin_names:
|
||||
|
|
@ -411,28 +398,25 @@ class lib:
|
|||
self.lib.write("{\n")
|
||||
self.lib.write(" direction : input; \n")
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
self.write_FF_setuphold()
|
||||
self.write_FF_setuphold(port)
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
def write_clk_timing_power(self):
|
||||
def write_clk_timing_power(self, port):
|
||||
""" Adds clk pin timing results."""
|
||||
|
||||
self.lib.write(" pin(clk){\n")
|
||||
self.lib.write(" pin(clk{0}){{\n".format(port))
|
||||
self.lib.write(" clock : true;\n")
|
||||
self.lib.write(" direction : input; \n")
|
||||
# FIXME: This depends on the clock buffer size in the control logic
|
||||
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
|
||||
|
||||
#Add power values for the ports. lib generated with this is not syntactically correct. TODO once
|
||||
#top level is done.
|
||||
for port in range(self.total_port_num):
|
||||
self.add_clk_control_power(port)
|
||||
self.add_clk_control_power(port)
|
||||
|
||||
min_pulse_width = round_time(self.char_results["min_period"])/2.0
|
||||
min_period = round_time(self.char_results["min_period"])
|
||||
min_pulse_width = round_time(self.char_sram_results["min_period"])/2.0
|
||||
min_period = round_time(self.char_sram_results["min_period"])
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
|
||||
self.lib.write(" related_pin : clk; \n")
|
||||
self.lib.write(" related_pin : clk{0}; \n".format(port))
|
||||
self.lib.write(" rise_constraint(scalar) {\n")
|
||||
self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width))
|
||||
self.lib.write(" }\n")
|
||||
|
|
@ -442,7 +426,7 @@ class lib:
|
|||
self.lib.write(" }\n")
|
||||
self.lib.write(" timing(){ \n")
|
||||
self.lib.write(" timing_type :\"minimum_period\"; \n")
|
||||
self.lib.write(" related_pin : clk; \n")
|
||||
self.lib.write(" related_pin : clk{0}; \n".format(port))
|
||||
self.lib.write(" rise_constraint(scalar) {\n")
|
||||
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
||||
self.lib.write(" }\n")
|
||||
|
|
@ -450,8 +434,7 @@ class lib:
|
|||
self.lib.write(" values(\"{0}\"); \n".format(min_period))
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n")
|
||||
self.lib.write(" }\n\n")
|
||||
|
||||
def add_clk_control_power(self, port):
|
||||
"""Writes powers under the clock pin group for a specified port"""
|
||||
|
|
@ -461,9 +444,9 @@ 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_results["write1_power{0}".format(port)] + self.char_results["write0_power{0}".format(port)])
|
||||
avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!CSb{0} & clk{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" when : \"!CSb{0} & clk{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
|
|
@ -475,9 +458,9 @@ class lib:
|
|||
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_results["read1_power{0}".format(port)] + self.char_results["read0_power{0}".format(port)])
|
||||
avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])
|
||||
self.lib.write(" internal_power(){\n")
|
||||
self.lib.write(" when : \"!CSb{0} & !clk{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" when : \"!CSb{0} & !clk{0}{1}\"; \n".format(port, web_name))
|
||||
self.lib.write(" rise_power(scalar){\n")
|
||||
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
|
||||
self.lib.write(" }\n")
|
||||
|
|
@ -502,13 +485,14 @@ class lib:
|
|||
if not hasattr(self,"d"):
|
||||
self.d = delay(self.sram, self.sp_file, self.corner)
|
||||
if self.use_model:
|
||||
self.char_results = self.d.analytical_delay(self.sram,self.slews,self.loads)
|
||||
char_results = self.d.analytical_delay(self.slews,self.loads)
|
||||
self.char_sram_results, self.char_port_results = char_results
|
||||
else:
|
||||
probe_address = "1" * self.sram.addr_size
|
||||
probe_data = self.sram.word_size - 1
|
||||
self.char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||
|
||||
|
||||
char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads)
|
||||
self.char_sram_results, self.char_port_results = char_results
|
||||
|
||||
def compute_setup_hold(self):
|
||||
""" Do the analysis if we haven't characterized a FF yet """
|
||||
# Do the analysis if we haven't characterized a FF yet
|
||||
|
|
@ -519,3 +503,158 @@ class lib:
|
|||
else:
|
||||
self.times = self.sh.analyze(self.slews,self.slews)
|
||||
|
||||
|
||||
def parse_info(self,corner,lib_name):
|
||||
""" Copies important characterization data to datasheet.info to be added to datasheet """
|
||||
if OPTS.is_unit_test:
|
||||
git_id = 'AAAAAAAAAAAAAAAAAAAA'
|
||||
else:
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
proc = subprocess.Popen(['git','rev-parse','HEAD'], cwd=os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/', stdout=subprocess.PIPE)
|
||||
|
||||
git_id = str(proc.stdout.read())
|
||||
|
||||
try:
|
||||
git_id = git_id[2:-3]
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(git_id) != 40:
|
||||
debug.warning("Failed to retrieve git id")
|
||||
git_id = 'Failed to retruieve'
|
||||
|
||||
datasheet = open(OPTS.openram_temp +'/datasheet.info', 'a+')
|
||||
|
||||
current_time = datetime.datetime.now()
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},".format(
|
||||
"sram_{0}_{1}_{2}".format(OPTS.word_size, OPTS.num_words, OPTS.tech_name),
|
||||
OPTS.num_words,
|
||||
OPTS.num_banks,
|
||||
OPTS.num_rw_ports,
|
||||
OPTS.num_w_ports,
|
||||
OPTS.num_r_ports,
|
||||
OPTS.tech_name,
|
||||
corner[2],
|
||||
corner[1],
|
||||
corner[0],
|
||||
round_time(self.char_sram_results["min_period"]),
|
||||
self.out_dir,
|
||||
lib_name,
|
||||
OPTS.word_size,
|
||||
git_id,
|
||||
current_time
|
||||
))
|
||||
|
||||
# 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))
|
||||
|
||||
for port in self.all_ports:
|
||||
#DIN timings
|
||||
if port in self.write_ports:
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
|
||||
"DIN{1}[{0}:0]".format(self.sram.word_size - 1, port),
|
||||
min(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
max(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
max(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
max(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_HL"]))),
|
||||
max(list(map(round_time,self.times["hold_times_HL"])))
|
||||
|
||||
))
|
||||
|
||||
for port in self.all_ports:
|
||||
#DOUT timing
|
||||
if port in self.read_ports:
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
|
||||
"DOUT{1}[{0}:0]".format(self.sram.word_size - 1, port),
|
||||
min(list(map(round_time,self.char_port_results[port]["delay_lh"]))),
|
||||
max(list(map(round_time,self.char_port_results[port]["delay_lh"]))),
|
||||
|
||||
min(list(map(round_time,self.char_port_results[port]["delay_hl"]))),
|
||||
max(list(map(round_time,self.char_port_results[port]["delay_hl"]))),
|
||||
|
||||
min(list(map(round_time,self.char_port_results[port]["slew_lh"]))),
|
||||
max(list(map(round_time,self.char_port_results[port]["slew_lh"]))),
|
||||
|
||||
min(list(map(round_time,self.char_port_results[port]["slew_hl"]))),
|
||||
max(list(map(round_time,self.char_port_results[port]["slew_hl"])))
|
||||
|
||||
|
||||
))
|
||||
|
||||
for port in self.all_ports:
|
||||
#CSb timings
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
|
||||
"CSb{0}".format(port),
|
||||
min(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
max(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
max(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
max(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_HL"]))),
|
||||
max(list(map(round_time,self.times["hold_times_HL"])))
|
||||
|
||||
))
|
||||
|
||||
for port in self.all_ports:
|
||||
#ADDR timings
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
|
||||
"ADDR{1}[{0}:0]".format(self.sram.addr_size - 1, port),
|
||||
min(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
max(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
max(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
max(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_HL"]))),
|
||||
max(list(map(round_time,self.times["hold_times_HL"])))
|
||||
|
||||
))
|
||||
|
||||
|
||||
for port in self.all_ports:
|
||||
if port in self.readwrite_ports:
|
||||
|
||||
#WEb timings
|
||||
datasheet.write("{0},{1},{2},{3},{4},{5},{6},{7},{8},".format(
|
||||
"WEb{0}".format(port),
|
||||
min(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
max(list(map(round_time,self.times["setup_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
max(list(map(round_time,self.times["setup_times_HL"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
max(list(map(round_time,self.times["hold_times_LH"]))),
|
||||
|
||||
min(list(map(round_time,self.times["hold_times_HL"]))),
|
||||
max(list(map(round_time,self.times["hold_times_HL"])))
|
||||
|
||||
))
|
||||
|
||||
|
||||
|
||||
|
||||
datasheet.write("END\n")
|
||||
datasheet.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
import debug
|
||||
from tech import drc, parameter, spice
|
||||
|
||||
class logical_effort():
|
||||
"""
|
||||
Class to support the values behind logical effort. Useful for storing the different components
|
||||
such as logical effort, electrical effort, and parasitic delay.
|
||||
"""
|
||||
beta = parameter["beta"]
|
||||
min_inv_cin = 1+beta
|
||||
pinv=parameter["min_inv_para_delay"]
|
||||
|
||||
def __init__(self, size, cin, cout, parasitic, out_is_rise=True):
|
||||
self.cin = cin
|
||||
self.cout = cout
|
||||
self.logical_effort = (self.cin/size)/logical_effort.min_inv_cin
|
||||
self.eletrical_effort = self.cout/self.cin
|
||||
self.parasitic_scale = parasitic
|
||||
self.is_rise = out_is_rise
|
||||
|
||||
def __str__(self):
|
||||
return "g=" + str(self.logical_effort) + ", h=" + str(self.eletrical_effort) + ", p=" + str(self.parasitic_scale)+"*pinv, rise_delay="+str(self.is_rise)
|
||||
|
||||
def get_stage_effort(self):
|
||||
return self.logical_effort*self.eletrical_effort
|
||||
|
||||
def get_parasitic_delay(self, pinv):
|
||||
return pinv * self.parasitic_scale
|
||||
|
||||
def get_stage_delay(self, pinv):
|
||||
return self.get_stage_effort()+self.get_parasitic_delay(pinv)
|
||||
|
||||
def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]):
|
||||
"""Calculates the total delay of a given delay path made of a list of logical effort objects."""
|
||||
total_rise_delay, total_fall_delay = calculate_relative_rise_fall_delays(stage_effort_list, pinv)
|
||||
return total_rise_delay + total_fall_delay
|
||||
|
||||
def calculate_relative_rise_fall_delays(stage_effort_list, pinv=parameter["min_inv_para_delay"]):
|
||||
"""Calculates the rise/fall delays of a given delay path made of a list of logical effort objects."""
|
||||
debug.info(2, "Calculating rise/fall relative delays")
|
||||
total_rise_delay, total_fall_delay = 0,0
|
||||
for stage in stage_effort_list:
|
||||
debug.info(3, stage)
|
||||
if stage.is_rise:
|
||||
total_rise_delay += stage.get_stage_delay(pinv)
|
||||
else:
|
||||
total_fall_delay += stage.get_stage_delay(pinv)
|
||||
return total_rise_delay, total_fall_delay
|
||||
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
import sys,re,shutil
|
||||
from design import design
|
||||
import debug
|
||||
import math
|
||||
import tech
|
||||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
|
||||
class simulation():
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
self.sram = sram
|
||||
|
||||
self.name = self.sram.name
|
||||
self.word_size = self.sram.word_size
|
||||
self.addr_size = self.sram.addr_size
|
||||
self.num_cols = self.sram.num_cols
|
||||
self.num_rows = self.sram.num_rows
|
||||
self.num_banks = self.sram.num_banks
|
||||
self.sp_file = spfile
|
||||
|
||||
self.all_ports = self.sram.all_ports
|
||||
self.readwrite_ports = self.sram.readwrite_ports
|
||||
self.read_ports = self.sram.read_ports
|
||||
self.write_ports = self.sram.write_ports
|
||||
|
||||
def set_corner(self,corner):
|
||||
""" Set the corner values """
|
||||
self.corner = corner
|
||||
(self.process, self.vdd_voltage, self.temperature) = corner
|
||||
|
||||
def set_spice_constants(self):
|
||||
""" sets feasible timing parameters """
|
||||
self.period = tech.spice["feasible_period"]
|
||||
self.slew = tech.spice["rise_time"]*2
|
||||
self.load = tech.spice["msflop_in_cap"]*4
|
||||
|
||||
self.v_high = self.vdd_voltage - tech.spice["v_threshold_typical"]
|
||||
self.v_low = tech.spice["v_threshold_typical"]
|
||||
self.gnd_voltage = 0
|
||||
|
||||
def set_stimulus_variables(self):
|
||||
# Clock signals
|
||||
self.cycle_times = []
|
||||
self.t_current = 0
|
||||
|
||||
# control signals: only one cs_b for entire multiported sram, one we_b for each write port
|
||||
self.csb_values = [[] for port in self.all_ports]
|
||||
self.web_values = [[] for port in self.readwrite_ports]
|
||||
|
||||
# Three dimensional list to handle each addr and data bits for wach port over the number of checks
|
||||
self.addr_values = [[[] for bit in range(self.addr_size)] for port in self.all_ports]
|
||||
self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports]
|
||||
|
||||
# For generating comments in SPICE stimulus
|
||||
self.cycle_comments = []
|
||||
self.fn_cycle_comments = []
|
||||
|
||||
def add_control_one_port(self, port, op):
|
||||
"""Appends control signals for operation to a given port"""
|
||||
#Determine values to write to port
|
||||
web_val = 1
|
||||
csb_val = 1
|
||||
if op == "read":
|
||||
csb_val = 0
|
||||
elif op == "write":
|
||||
csb_val = 0
|
||||
web_val = 0
|
||||
elif op != "noop":
|
||||
debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1)
|
||||
|
||||
# Append the values depending on the type of port
|
||||
self.csb_values[port].append(csb_val)
|
||||
# If port is in both lists, add rw control signal. Condition indicates its a RW port.
|
||||
if port in self.readwrite_ports:
|
||||
self.web_values[port].append(web_val)
|
||||
|
||||
def add_data(self, data, port):
|
||||
""" Add the array of data values """
|
||||
debug.check(len(data)==self.word_size, "Invalid data word size.")
|
||||
|
||||
bit = self.word_size - 1
|
||||
for c in data:
|
||||
if c=="0":
|
||||
self.data_values[port][bit].append(0)
|
||||
elif c=="1":
|
||||
self.data_values[port][bit].append(1)
|
||||
else:
|
||||
debug.error("Non-binary data string",1)
|
||||
bit -= 1
|
||||
|
||||
def add_address(self, address, port):
|
||||
""" Add the array of address values """
|
||||
debug.check(len(address)==self.addr_size, "Invalid address size.")
|
||||
|
||||
bit = self.addr_size - 1
|
||||
for c in address:
|
||||
if c=="0":
|
||||
self.addr_values[port][bit].append(0)
|
||||
elif c=="1":
|
||||
self.addr_values[port][bit].append(1)
|
||||
else:
|
||||
debug.error("Non-binary address string",1)
|
||||
bit -= 1
|
||||
|
||||
def add_write(self, comment, address, data, port):
|
||||
""" Add the control values for a write cycle. """
|
||||
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
|
||||
debug.info(2, comment)
|
||||
self.fn_cycle_comments.append(comment)
|
||||
self.append_cycle_comment(port, comment)
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
||||
self.add_control_one_port(port, "write")
|
||||
self.add_data(data,port)
|
||||
self.add_address(address,port)
|
||||
|
||||
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
|
||||
noop_data = "0"*self.word_size
|
||||
#Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(address, noop_data, unselected_port)
|
||||
|
||||
def add_read(self, comment, address, din_data, port):
|
||||
""" Add the control values for a read cycle. """
|
||||
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
|
||||
debug.info(2, comment)
|
||||
self.fn_cycle_comments.append(comment)
|
||||
self.append_cycle_comment(port, comment)
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
self.add_control_one_port(port, "read")
|
||||
|
||||
#If the port is also a readwrite then add data.
|
||||
if port in self.write_ports:
|
||||
self.add_data(din_data,port)
|
||||
self.add_address(address, port)
|
||||
|
||||
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
|
||||
noop_data = "0"*self.word_size
|
||||
#Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(address, noop_data, unselected_port)
|
||||
|
||||
def add_noop_all_ports(self, comment, address, data):
|
||||
""" Add the control values for a noop to all ports. """
|
||||
debug.info(2, comment)
|
||||
self.fn_cycle_comments.append(comment)
|
||||
self.append_cycle_comment("All", comment)
|
||||
|
||||
self.cycle_times.append(self.t_current)
|
||||
self.t_current += self.period
|
||||
|
||||
for port in self.all_ports:
|
||||
self.add_noop_one_port(address, data, port)
|
||||
|
||||
def add_write_one_port(self, comment, address, data, port):
|
||||
""" Add the control values for a write cycle. Does not increment the period. """
|
||||
debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports))
|
||||
debug.info(2, comment)
|
||||
self.fn_cycle_comments.append(comment)
|
||||
|
||||
self.add_control_one_port(port, "write")
|
||||
self.add_data(data,port)
|
||||
self.add_address(address,port)
|
||||
|
||||
def add_read_one_port(self, comment, address, din_data, port):
|
||||
""" Add the control values for a read cycle. Does not increment the period. """
|
||||
debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports))
|
||||
debug.info(2, comment)
|
||||
self.fn_cycle_comments.append(comment)
|
||||
|
||||
self.add_control_one_port(port, "read")
|
||||
#If the port is also a readwrite then add data.
|
||||
if port in self.write_ports:
|
||||
self.add_data(din_data,port)
|
||||
self.add_address(address, port)
|
||||
|
||||
def add_noop_one_port(self, address, data, port):
|
||||
""" Add the control values for a noop to a single port. Does not increment the period. """
|
||||
self.add_control_one_port(port, "noop")
|
||||
if port in self.write_ports:
|
||||
self.add_data(data,port)
|
||||
self.add_address(address, port)
|
||||
|
||||
def append_cycle_comment(self, port, comment):
|
||||
"""Add comment to list to be printed in stimulus file"""
|
||||
#Clean up time before appending. Make spacing dynamic as well.
|
||||
time = "{0:.2f} ns:".format(self.t_current)
|
||||
time_spacing = len(time)+6
|
||||
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
||||
port,
|
||||
time,
|
||||
time_spacing,
|
||||
comment))
|
||||
|
||||
def gen_cycle_comment(self, op, word, addr, port, t_current):
|
||||
if op == "noop":
|
||||
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
|
||||
t_current,
|
||||
t_current+self.period)
|
||||
elif op == "write":
|
||||
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||
addr,
|
||||
port,
|
||||
int(t_current/self.period),
|
||||
t_current,
|
||||
t_current+self.period)
|
||||
else:
|
||||
comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||
addr,
|
||||
port,
|
||||
int(t_current/self.period),
|
||||
t_current,
|
||||
t_current+self.period)
|
||||
return comment
|
||||
|
||||
|
|
@ -28,39 +28,53 @@ class stimuli():
|
|||
|
||||
(self.process, self.voltage, self.temperature) = corner
|
||||
self.device_models = tech.spice["fet_models"][self.process]
|
||||
|
||||
|
||||
def inst_sram(self, abits, dbits, port_info, sram_name):
|
||||
def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name):
|
||||
""" Function to instatiate an SRAM subckt. """
|
||||
pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits)
|
||||
#Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM
|
||||
debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names))
|
||||
|
||||
self.sf.write("Xsram ")
|
||||
|
||||
#Un-tuple the port names. This was done to avoid passing them all as arguments. Could be improved still.
|
||||
#This should be generated from the pin list of the sram... change when multiport pins done.
|
||||
(total_port_num,readwrite_num,read_ports,write_ports) = port_info
|
||||
|
||||
for write_input in write_ports:
|
||||
for i in range(dbits):
|
||||
self.sf.write("DIN{0}[{1}] ".format(write_input, i))
|
||||
|
||||
for port in range(total_port_num):
|
||||
for i in range(abits):
|
||||
self.sf.write("A{0}[{1}] ".format(port,i))
|
||||
|
||||
#These control signals assume 6t sram i.e. a single readwrite port. If multiple readwrite ports are used then add more
|
||||
#control signals. Not sure if this is correct, consider a temporary change until control signals for multiport are finalized.
|
||||
for port in range(total_port_num):
|
||||
self.sf.write("CSB{0} ".format(port))
|
||||
for readwrite_port in range(readwrite_num):
|
||||
self.sf.write("WEB{0} ".format(readwrite_port))
|
||||
|
||||
self.sf.write("{0} ".format(tech.spice["clk"]))
|
||||
for read_output in read_ports:
|
||||
for i in range(dbits):
|
||||
self.sf.write("DOUT{0}[{1}] ".format(read_output, i))
|
||||
self.sf.write("{0} {1} ".format(self.vdd_name, self.gnd_name))
|
||||
for pin in pin_names:
|
||||
self.sf.write("{0} ".format(pin))
|
||||
self.sf.write("{0}\n".format(sram_name))
|
||||
|
||||
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
|
||||
"""Creates the pins names of the SRAM based on the no. of ports."""
|
||||
#This may seem redundant as the pin names are already defined in the sram. However, it is difficult to extract the
|
||||
#functionality from the names, so they are recreated. As the order is static, changing the order of the pin names
|
||||
#will cause issues here.
|
||||
pin_names = []
|
||||
(addr_name, din_name, dout_name) = port_signal_names
|
||||
(total_ports, write_index, read_index) = port_info
|
||||
|
||||
for write_input in write_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(din_name,write_input, i))
|
||||
|
||||
for port in range(total_ports):
|
||||
for i in range(abits):
|
||||
pin_names.append("{0}{1}_{2}".format(addr_name,port,i))
|
||||
|
||||
#Control signals not finalized.
|
||||
for port in range(total_ports):
|
||||
pin_names.append("CSB{0}".format(port))
|
||||
for port in range(total_ports):
|
||||
if (port in read_index) and (port in write_index):
|
||||
pin_names.append("WEB{0}".format(port))
|
||||
|
||||
for port in range(total_ports):
|
||||
pin_names.append("{0}{1}".format(tech.spice["clk"], port))
|
||||
|
||||
for read_output in read_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i))
|
||||
|
||||
pin_names.append("{0}".format(self.vdd_name))
|
||||
pin_names.append("{0}".format(self.gnd_name))
|
||||
return pin_names
|
||||
|
||||
def inst_model(self, pins, model_name):
|
||||
""" Function to instantiate a generic model with a set of pins """
|
||||
self.sf.write("X{0} ".format(model_name))
|
||||
|
|
@ -153,7 +167,7 @@ class stimuli():
|
|||
to the initial value.
|
||||
"""
|
||||
# the initial value is not a clock time
|
||||
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match.")
|
||||
debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match. {0} clock values, {1} data values for {2}".format(len(clk_times), len(data_values), sig_name))
|
||||
|
||||
# shift signal times earlier for setup time
|
||||
times = np.array(clk_times) - setup*period
|
||||
|
|
@ -202,6 +216,16 @@ class stimuli():
|
|||
targ_dir,
|
||||
targ_td))
|
||||
|
||||
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name,
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
trig_td))
|
||||
|
||||
def gen_meas_power(self, meas_name, t_initial, t_final):
|
||||
""" Creates the .meas statement for the measurement of avg power """
|
||||
# power mea cmd is different in different spice:
|
||||
|
|
@ -213,19 +237,35 @@ class stimuli():
|
|||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def gen_meas_value(self, meas_name, dout, t_intital, t_final):
|
||||
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
|
||||
self.sf.write(measure_string)
|
||||
|
||||
def write_control(self, end_time):
|
||||
def write_control(self, end_time, runlvl=4):
|
||||
""" Write the control cards to run and end the simulation """
|
||||
|
||||
# These are guesses...
|
||||
if runlvl==1:
|
||||
reltol = 0.02 # 2%
|
||||
elif runlvl==2:
|
||||
reltol = 0.01 # 1%
|
||||
elif runlvl==3:
|
||||
reltol = 0.005 # 0.5%
|
||||
else:
|
||||
reltol = 0.001 # 0.1%
|
||||
timestep = 10 #ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
|
||||
|
||||
# UIC is needed for ngspice to converge
|
||||
self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time))
|
||||
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep,end_time))
|
||||
if OPTS.spice_name == "ngspice":
|
||||
# ngspice sometimes has convergence problems if not using gear method
|
||||
# which is more accurate, but slower than the default trapezoid method
|
||||
# Do not remove this or it may not converge due to some "pa_00" nodes
|
||||
# unless you figure out what these are.
|
||||
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature))
|
||||
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature))
|
||||
else:
|
||||
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature))
|
||||
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))
|
||||
|
||||
# create plots for all signals
|
||||
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
|
||||
|
|
@ -255,12 +295,15 @@ class stimuli():
|
|||
|
||||
def write_supply(self):
|
||||
""" Writes supply voltage statements """
|
||||
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.vdd_name, self.voltage))
|
||||
self.sf.write("V{0} {0} 0.0 {1}\n".format(self.gnd_name, 0))
|
||||
gnd_node_name = "0"
|
||||
self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage))
|
||||
# This is for the test power supply
|
||||
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.vdd_name, self.voltage))
|
||||
self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.gnd_name, 0))
|
||||
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.vdd_name, gnd_node_name, self.voltage))
|
||||
self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.gnd_name, gnd_node_name, 0.0))
|
||||
|
||||
#Adding a commented out supply for simulators where gnd and 0 are not global grounds.
|
||||
self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n")
|
||||
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
|
||||
|
||||
def run_sim(self):
|
||||
""" Run hspice in batch mode and output rawfile to parse. """
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import debug
|
||||
from math import log
|
||||
import re
|
||||
|
||||
class trim_spice():
|
||||
"""
|
||||
|
|
@ -49,13 +50,13 @@ class trim_spice():
|
|||
|
||||
# Split up the address and convert to an int
|
||||
wl_address = int(address[self.col_addr_size:],2)
|
||||
if self.col_addr_size>1:
|
||||
if self.col_addr_size>0:
|
||||
col_address = int(address[0:self.col_addr_size],2)
|
||||
else:
|
||||
col_address = 0
|
||||
# 1. Keep cells in the bitcell array based on WL and BL
|
||||
wl_name = "wl[{}]".format(wl_address)
|
||||
bl_name = "bl[{}]".format(int(self.words_per_row*data_bit + col_address))
|
||||
wl_name = "wl_{}".format(wl_address)
|
||||
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
|
||||
# Prepend info about the trimming
|
||||
addr_msg = "Keeping {} address".format(address)
|
||||
|
|
@ -73,25 +74,28 @@ class trim_spice():
|
|||
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
|
||||
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
|
||||
|
||||
self.remove_insts("bitcell_array",[wl_name,bl_name])
|
||||
|
||||
wl_regex = r"wl\d*_{}".format(wl_address)
|
||||
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
|
||||
|
||||
# 2. Keep sense amps basd on BL
|
||||
# FIXME: The bit lines are not indexed the same in sense_amp_array
|
||||
#self.remove_insts("sense_amp_array",[bl_name])
|
||||
#self.remove_insts("sense_amp_array",[bl_regex])
|
||||
|
||||
# 3. Keep column muxes basd on BL
|
||||
self.remove_insts("column_mux_array",[bl_name])
|
||||
self.remove_insts("column_mux_array",[bl_regex])
|
||||
|
||||
# 4. Keep write driver based on DATA
|
||||
data_name = "data[{}]".format(data_bit)
|
||||
self.remove_insts("write_driver_array",[data_name])
|
||||
data_regex = r"data_{}".format(data_bit)
|
||||
self.remove_insts("write_driver_array",[data_regex])
|
||||
|
||||
# 5. Keep wordline driver based on WL
|
||||
# Need to keep the gater too
|
||||
#self.remove_insts("wordline_driver",wl_name)
|
||||
#self.remove_insts("wordline_driver",wl_regex)
|
||||
|
||||
# 6. Keep precharges based on BL
|
||||
self.remove_insts("precharge_array",[bl_name])
|
||||
self.remove_insts("precharge_array",[bl_regex])
|
||||
|
||||
# Everything else isn't worth removing. :)
|
||||
|
||||
|
|
@ -107,6 +111,10 @@ class trim_spice():
|
|||
match of the line with a term so you can search for a single
|
||||
net connection, the instance name, anything..
|
||||
"""
|
||||
removed_insts = 0
|
||||
#Expects keep_inst_list are regex patterns. Compile them here.
|
||||
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
|
||||
|
||||
start_name = ".SUBCKT {}".format(subckt_name)
|
||||
end_name = ".ENDS {}".format(subckt_name)
|
||||
|
||||
|
|
@ -120,11 +128,14 @@ class trim_spice():
|
|||
new_buffer.append(line)
|
||||
in_subckt=False
|
||||
elif in_subckt:
|
||||
for k in keep_inst_list:
|
||||
if k in line:
|
||||
removed_insts += 1
|
||||
for pattern in compiled_patterns:
|
||||
if pattern.search(line) != None:
|
||||
new_buffer.append(line)
|
||||
removed_insts -= 1
|
||||
break
|
||||
else:
|
||||
new_buffer.append(line)
|
||||
|
||||
self.sp_buffer = new_buffer
|
||||
debug.info(2, "Removed {} instances from {} subcircuit.".format(removed_insts, subckt_name))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
import sys,re,shutil
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from .delay import delay
|
||||
|
||||
class worst_case(delay):
|
||||
"""Functions to test for the worst case delay in a target SRAM
|
||||
|
||||
The current worst case determines a feasible period for the SRAM then tests
|
||||
several bits and record the delay and differences between the bits.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
delay.__init__(self,sram,spfile,corner)
|
||||
|
||||
|
||||
def analyze(self,probe_address, probe_data, slews, loads):
|
||||
"""
|
||||
Main function to test the delays of different bits.
|
||||
"""
|
||||
debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 ,
|
||||
"Bit testing does not currently support multiport.")
|
||||
#Dict to hold all characterization values
|
||||
char_sram_data = {}
|
||||
|
||||
self.set_probe(probe_address, probe_data)
|
||||
#self.prepare_netlist()
|
||||
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
|
||||
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
|
||||
feasible_delays = self.find_feasible_period()
|
||||
|
||||
# 2) Find the delays of several bits
|
||||
test_bits = self.get_test_bits()
|
||||
bit_delays = self.simulate_for_bit_delays(test_bits)
|
||||
|
||||
for i in range(len(test_bits)):
|
||||
debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i]))
|
||||
|
||||
def simulate_for_bit_delays(self, test_bits):
|
||||
"""Simulates the delay of the sram of over several bits."""
|
||||
bit_delays = [{} for i in range(len(test_bits))]
|
||||
|
||||
#Assumes a bitcell with only 1 rw port. (6t, port 0)
|
||||
port = 0
|
||||
self.targ_read_ports = [self.read_ports[port]]
|
||||
self.targ_write_ports = [self.write_ports[port]]
|
||||
|
||||
for i in range(len(test_bits)):
|
||||
(bit_addr, bit_data) = test_bits[i]
|
||||
self.set_probe(bit_addr, bit_data)
|
||||
debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
|
||||
(success, results)=self.run_delay_simulation()
|
||||
debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
|
||||
bit_delays[i] = results[port]
|
||||
|
||||
return bit_delays
|
||||
|
||||
|
||||
def get_test_bits(self):
|
||||
"""Statically determines address and bit values to test"""
|
||||
#First and last address, first middle, and last bit. Last bit is repeated twice with different data position.
|
||||
bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size]
|
||||
data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1]
|
||||
#Return them in a tuple form
|
||||
return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))]
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<style>
|
||||
#data {
|
||||
font-family: Trebuchet MS, Arial, Helvetica, sans-serif;
|
||||
border-collapse: collapse;
|
||||
width: 99%;
|
||||
max-width: 799px
|
||||
}
|
||||
|
||||
#data td, #data th {
|
||||
border: 0px solid #ddd;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
#data tr:nth-child(even){background-color: #f1f2f2;}
|
||||
|
||||
#data tr:hover {background-color: #ddd;}
|
||||
|
||||
#data th {
|
||||
padding-top: 11px;
|
||||
padding-bottom: 11px;
|
||||
text-align: left;
|
||||
background-color: #004184;
|
||||
color: #F1B521;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
|
|
@ -0,0 +1,76 @@
|
|||
from table_gen import *
|
||||
import os
|
||||
import csv
|
||||
import base64
|
||||
from globals import OPTS
|
||||
|
||||
class datasheet():
|
||||
"""
|
||||
Defines the layout,but not the data, of the html datasheet
|
||||
"""
|
||||
def __init__(self,identifier):
|
||||
self.name = identifier
|
||||
self.html = ""
|
||||
|
||||
|
||||
def generate_html(self):
|
||||
"""
|
||||
Generates html tables using flask-table
|
||||
"""
|
||||
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/datasheet.css', 'r') as datasheet_css:
|
||||
#css styling is kept in a seperate file
|
||||
self.html += datasheet_css.read()
|
||||
|
||||
|
||||
# with open(OPTS.openram_temp + "/datasheet.info") as info:
|
||||
self.html += '<!--'
|
||||
# for row in info:
|
||||
# self.html += row
|
||||
for item in self.description:
|
||||
self.html += item + ','
|
||||
self.html += 'EOL'
|
||||
self.html +='-->'
|
||||
|
||||
vlsi_logo = 0
|
||||
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/vlsi_logo.png' , "rb") as image_file:
|
||||
vlsi_logo = base64.b64encode(image_file.read())
|
||||
|
||||
openram_logo = 0
|
||||
with open(os.path.abspath(os.environ.get("OPENRAM_HOME")) + '/datasheet/assets/openram_logo_placeholder.png' , "rb") as image_file:
|
||||
openram_logo = base64.b64encode(image_file.read())
|
||||
|
||||
|
||||
self.html += '<a href="https://vlsida.soe.ucsc.edu/"><img src="data:image/png;base64,{0}" alt="VLSIDA"></a>'.format(str(vlsi_logo)[2:-1])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.html +='<p style="font-size: 18px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">'+ self.name + '.html' + '</p>'
|
||||
self.html +='<p style="font-size: 18px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Compiled at: '+ self.time + '</p>'
|
||||
self.html +='<p style="font-size: 18px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">'+ 'DRC errors: ' + str(self.DRC) + '</p>'
|
||||
self.html +='<p style="font-size: 18px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">'+ 'LVS errors: ' + str(self.LVS) + '</p>'
|
||||
self.html += '<p style="font-size: 18px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">'+ 'Git commit id: ' + str(self.git_id) + '</p>'
|
||||
|
||||
self.html +='<p style="font-size: 26px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Ports and Configuration</p>'
|
||||
# self.html += in_out(self.io,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
|
||||
self.html += self.io_table.to_html()
|
||||
|
||||
self.html +='<p style="font-size: 26px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Operating Conditions</p>'
|
||||
# self.html += operating_conditions(self.operating,table_id='data').__html__()
|
||||
self.html += self.operating_table.to_html()
|
||||
|
||||
self.html += '<p style="font-size: 26px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Timing and Current Data</p>'
|
||||
# self.html += timing_and_current_data(self.timing,table_id='data').__html__()
|
||||
self.html += self.timing_table.to_html()
|
||||
|
||||
self.html += '<p style="font-size: 26px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Characterization Corners</p>'
|
||||
# self.html += characterization_corners(self.corners,table_id='data').__html__()
|
||||
self.html += self.corners_table.to_html()
|
||||
|
||||
self.html +='<p style="font-size: 26px;font-family: Trebuchet MS, Arial, Helvetica, sans-serif;">Deliverables</p>'
|
||||
# self.html += deliverables(self.dlv,table_id='data').__html__().replace('<','<').replace('"','"').replace('>',">")
|
||||
self.html += self.dlv_table.to_html()
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,530 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
This is a script to load data from the characterization and layout processes into
|
||||
a web friendly html datasheet.
|
||||
"""
|
||||
#TODO:
|
||||
#include log file
|
||||
#Diagram generation
|
||||
#Improve css
|
||||
|
||||
|
||||
import debug
|
||||
from globals import OPTS
|
||||
import os, math
|
||||
import optparse
|
||||
import csv
|
||||
from datasheet import *
|
||||
from table_gen import *
|
||||
|
||||
def process_name(corner):
|
||||
"""
|
||||
Expands the names of the characterization corner types into something human friendly
|
||||
"""
|
||||
if corner == "TT":
|
||||
return "Typical - Typical"
|
||||
if corner == "SS":
|
||||
return "Slow - Slow"
|
||||
if corner == "FF":
|
||||
return "Fast - Fast"
|
||||
else:
|
||||
return "custom"
|
||||
|
||||
def parse_characterizer_csv(sram,f,pages):
|
||||
"""
|
||||
Parses output data of the Liberty file generator in order to construct the timing and
|
||||
current table
|
||||
"""
|
||||
with open(f) as csv_file:
|
||||
csv_reader = csv.reader(csv_file, delimiter=',')
|
||||
line_count = 0
|
||||
for row in csv_reader:
|
||||
|
||||
found = 0
|
||||
col = 0
|
||||
|
||||
#defines layout of csv file
|
||||
NAME = row[col]
|
||||
col += 1
|
||||
|
||||
NUM_WORDS = row[col]
|
||||
col += 1
|
||||
|
||||
NUM_BANKS = row[col]
|
||||
col += 1
|
||||
|
||||
NUM_RW_PORTS = row[col]
|
||||
col += 1
|
||||
|
||||
NUM_W_PORTS = row[col]
|
||||
col += 1
|
||||
|
||||
NUM_R_PORTS = row[col]
|
||||
col += 1
|
||||
|
||||
TECH_NAME = row[col]
|
||||
col += 1
|
||||
|
||||
TEMP = row[col]
|
||||
col += 1
|
||||
|
||||
VOLT = row[col]
|
||||
col += 1
|
||||
|
||||
PROC = row[col]
|
||||
col += 1
|
||||
|
||||
MIN_PERIOD = row[col]
|
||||
col += 1
|
||||
|
||||
OUT_DIR = row[col]
|
||||
col += 1
|
||||
|
||||
LIB_NAME = row[col]
|
||||
col += 1
|
||||
|
||||
WORD_SIZE = row[col]
|
||||
col += 1
|
||||
|
||||
ORIGIN_ID = row[col]
|
||||
col += 1
|
||||
|
||||
DATETIME = row[col]
|
||||
col+= 1
|
||||
|
||||
DRC = row[col]
|
||||
col += 1
|
||||
|
||||
LVS = row[col]
|
||||
col += 1
|
||||
|
||||
for sheet in pages:
|
||||
|
||||
|
||||
if sheet.name == NAME:
|
||||
|
||||
found = 1
|
||||
#if the .lib information is for an existing datasheet compare timing data
|
||||
|
||||
for item in sheet.operating_table.rows:
|
||||
#check if the new corner data is worse than the previous worse corner data
|
||||
|
||||
if item[0] == 'Operating Temperature':
|
||||
if float(TEMP) > float(item[3]):
|
||||
item[2] = item[3]
|
||||
item[3] = TEMP
|
||||
if float(TEMP) < float(item[1]):
|
||||
item[2] = item[1]
|
||||
item[1] = TEMP
|
||||
|
||||
if item[0] == 'Power supply (VDD) range':
|
||||
if float(VOLT) > float(item[3]):
|
||||
item[2] = item[3]
|
||||
item[3] = VOLT
|
||||
if float(VOLT) < float(item[1]):
|
||||
item[2] = item[1]
|
||||
item[1] = VOLT
|
||||
|
||||
if item[0] == 'Operating Frequncy (F)':
|
||||
try:
|
||||
if float(math.floor(1000/float(MIN_PERIOD)) < float(item[3])):
|
||||
item[3] = str(math.floor(1000/float(MIN_PERIOD)))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
while(True):
|
||||
|
||||
|
||||
if(row[col].startswith('DIN')):
|
||||
start = col
|
||||
for item in sheet.timing_table.rows:
|
||||
if item[0].startswith(row[col]):
|
||||
|
||||
if item[0].endswith('setup rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('setup falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
col += 1
|
||||
|
||||
elif(row[col].startswith('DOUT')):
|
||||
start = col
|
||||
for item in sheet.timing_table.rows:
|
||||
if item[0].startswith(row[col]):
|
||||
|
||||
if item[0].endswith('cell rise'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('cell fall'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('rise transition'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('fall transition'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
col += 1
|
||||
|
||||
elif(row[col].startswith('CSb')):
|
||||
start = col
|
||||
for item in sheet.timing_table.rows:
|
||||
if item[0].startswith(row[col]):
|
||||
|
||||
if item[0].endswith('setup rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('setup falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
col += 1
|
||||
|
||||
|
||||
elif(row[col].startswith('WEb')):
|
||||
start = col
|
||||
for item in sheet.timing_table.rows:
|
||||
if item[0].startswith(row[col]):
|
||||
|
||||
if item[0].endswith('setup rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('setup falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
col += 1
|
||||
|
||||
|
||||
elif(row[col].startswith('ADDR')):
|
||||
start = col
|
||||
for item in sheet.timing_table.rows:
|
||||
if item[0].startswith(row[col]):
|
||||
|
||||
if item[0].endswith('setup rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('setup falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold rising'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
elif item[0].endswith('hold falling'):
|
||||
if float(row[col+1]) < float(item[1]):
|
||||
item[1] = row[col+1]
|
||||
if float(row[col+2]) > float(item[2]):
|
||||
item[2] = row[col+2]
|
||||
|
||||
col += 2
|
||||
|
||||
col += 1
|
||||
|
||||
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
new_sheet.corners_table.add_row([PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')])
|
||||
new_sheet.dlv_table.add_row(['.lib','Synthesis models','<a href="file://{0}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))])
|
||||
|
||||
if found == 0:
|
||||
|
||||
#if this is the first corner for this sram, run first time configuration and set up tables
|
||||
new_sheet = datasheet(NAME)
|
||||
pages.append(new_sheet)
|
||||
|
||||
new_sheet.git_id = ORIGIN_ID
|
||||
new_sheet.time = DATETIME
|
||||
new_sheet.DRC = DRC
|
||||
new_sheet.LVS = LVS
|
||||
new_sheet.description = [NAME, NUM_WORDS, NUM_BANKS, NUM_RW_PORTS, NUM_W_PORTS, NUM_R_PORTS, TECH_NAME, WORD_SIZE, ORIGIN_ID, DATETIME]
|
||||
|
||||
new_sheet.corners_table = table_gen("corners")
|
||||
new_sheet.corners_table.add_row(['Corner Name','Process','Power Supply','Temperature','Library Name Suffix'])
|
||||
new_sheet.corners_table.add_row([PROC,process_name(PROC),VOLT,TEMP,LIB_NAME.replace(OUT_DIR,'').replace(NAME,'')])
|
||||
new_sheet.operating_table = table_gen("operating_table")
|
||||
new_sheet.operating_table.add_row(['Parameter','Min','Typ','Max','Units'])
|
||||
new_sheet.operating_table.add_row(['Power supply (VDD) range',VOLT,VOLT,VOLT,'Volts'])
|
||||
new_sheet.operating_table.add_row(['Operating Temperature',TEMP,TEMP,TEMP,'Celsius'])
|
||||
|
||||
try:
|
||||
new_sheet.operating_table.add_row(['Operating Frequency (F)','','',str(math.floor(1000/float(MIN_PERIOD))),'MHz'])
|
||||
except Exception:
|
||||
new_sheet.operating_table.add_row(['Operating Frequency (F)','','',"not available in netlist only",'MHz']) #failed to provide non-zero MIN_PERIOD
|
||||
new_sheet.timing_table = table_gen("timing")
|
||||
new_sheet.timing_table.add_row(['Parameter','Min','Max','Units'])
|
||||
while(True):
|
||||
if(row[col].startswith('DIN')):
|
||||
start = col
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
col += 2
|
||||
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
col +=1
|
||||
|
||||
elif(row[col].startswith('DOUT')):
|
||||
start = col
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} cell rise'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
col += 2
|
||||
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} cell fall'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} rise transition'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} fall transition'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
col +=1
|
||||
|
||||
elif(row[col].startswith('CSb')):
|
||||
start = col
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
col += 2
|
||||
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
col +=1
|
||||
|
||||
elif(row[col].startswith('WEb')):
|
||||
start = col
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
col += 2
|
||||
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
col +=1
|
||||
|
||||
elif(row[col].startswith('ADDR')):
|
||||
start = col
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
col += 2
|
||||
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} setup falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold rising'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
new_sheet.timing_table.add_row(['{0} hold falling'.format(row[start]),row[col+1],row[col+2],'ns'])
|
||||
|
||||
col += 2
|
||||
|
||||
col +=1
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
|
||||
new_sheet.dlv_table = table_gen("dlv")
|
||||
new_sheet.dlv_table.add_row(['Type','Description','Link'])
|
||||
|
||||
new_sheet.io_table = table_gen("io")
|
||||
new_sheet.io_table.add_row(['Type', 'Value'])
|
||||
|
||||
if not OPTS.netlist_only:
|
||||
#physical layout files should not be generated in netlist only mode
|
||||
new_sheet.dlv_table.add_row(['.gds','GDSII layout views','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'gds')])
|
||||
new_sheet.dlv_table.add_row(['.lef','LEF files','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'lef')])
|
||||
|
||||
|
||||
new_sheet.dlv_table.add_row(['.sp','SPICE netlists','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'sp')])
|
||||
new_sheet.dlv_table.add_row(['.v','Verilog simulation models','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'v')])
|
||||
new_sheet.dlv_table.add_row(['.html','This datasheet','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'html')])
|
||||
new_sheet.dlv_table.add_row(['.lib','Synthesis models','<a href="{1}">{1}</a>'.format(LIB_NAME,LIB_NAME.replace(OUT_DIR,''))])
|
||||
new_sheet.dlv_table.add_row(['.py','OpenRAM configuration file','<a href="{0}.{1}">{0}.{1}</a>'.format(OPTS.output_name,'py')])
|
||||
|
||||
new_sheet.io_table.add_row(['WORD_SIZE',WORD_SIZE])
|
||||
new_sheet.io_table.add_row(['NUM_WORDS',NUM_WORDS])
|
||||
new_sheet.io_table.add_row(['NUM_BANKS',NUM_BANKS])
|
||||
new_sheet.io_table.add_row(['NUM_RW_PORTS',NUM_RW_PORTS])
|
||||
new_sheet.io_table.add_row(['NUM_R_PORTS',NUM_R_PORTS])
|
||||
new_sheet.io_table.add_row(['NUM_W_PORTS',NUM_W_PORTS])
|
||||
new_sheet.io_table.add_row(['Area',sram.width * sram.height])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class datasheet_gen():
|
||||
def datasheet_write(sram,name):
|
||||
|
||||
|
||||
in_dir = OPTS.openram_temp
|
||||
|
||||
if not (os.path.isdir(in_dir)):
|
||||
os.mkdir(in_dir)
|
||||
|
||||
|
||||
datasheets = []
|
||||
parse_characterizer_csv(sram, in_dir + "/datasheet.info", datasheets)
|
||||
|
||||
|
||||
for sheets in datasheets:
|
||||
with open(name, 'w+') as f:
|
||||
sheets.generate_html()
|
||||
f.write(sheets.html)
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
class table_gen:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
self.rows = []
|
||||
self.table_id = 'data'
|
||||
|
||||
def add_row(self,row):
|
||||
self.rows.append(row)
|
||||
|
||||
def gen_table_head(self):
|
||||
html = ''
|
||||
|
||||
html += '<thead>'
|
||||
html += '<tr>'
|
||||
for col in self.rows[0]:
|
||||
html += '<th>' + str(col) + '</th>'
|
||||
html += '</tr>'
|
||||
html += '</thead>'
|
||||
return html
|
||||
|
||||
def gen_table_body(self):
|
||||
html = ''
|
||||
|
||||
html += '<tbody>'
|
||||
html += '<tr>'
|
||||
for row in self.rows[1:]:
|
||||
html += '<tr>'
|
||||
for col in row:
|
||||
html += '<td>' + str(col) + '</td>'
|
||||
html += '</tr>'
|
||||
html += '</tr>'
|
||||
html += '</tbody>'
|
||||
return html
|
||||
|
||||
def to_html(self):
|
||||
|
||||
html = ''
|
||||
html += '<table id= \"'+self.table_id+'\">'
|
||||
html += self.gen_table_head()
|
||||
html += self.gen_table_body()
|
||||
html += '</table>'
|
||||
|
||||
return html
|
||||
|
|
@ -0,0 +1,827 @@
|
|||
#!/usr/bin/python
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2011-2016 Aliaksei Chapyzhenka
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# Translated to Python from original file:
|
||||
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
|
||||
#
|
||||
|
||||
import sys
|
||||
import json
|
||||
import math
|
||||
import waveskin
|
||||
|
||||
font_width = 7
|
||||
|
||||
lane = {
|
||||
"xs" : 20, # tmpgraphlane0.width
|
||||
"ys" : 20, # tmpgraphlane0.height
|
||||
"xg" : 120, # tmpgraphlane0.x
|
||||
"yg" : 0, # head gap
|
||||
"yh0" : 0, # head gap title
|
||||
"yh1" : 0, # head gap
|
||||
"yf0" : 0, # foot gap
|
||||
"yf1" : 0, # foot gap
|
||||
"y0" : 5, # tmpgraphlane0.y
|
||||
"yo" : 30, # tmpgraphlane1.y - y0
|
||||
"tgo" : -10, # tmptextlane0.x - xg
|
||||
"ym" : 15, # tmptextlane0.y - y0
|
||||
"xlabel" : 6, # tmptextlabel.x - xg
|
||||
"xmax" : 1,
|
||||
"scale" : 1,
|
||||
"head" : {},
|
||||
"foot" : {}
|
||||
}
|
||||
|
||||
def genBrick (texts, extra, times) :
|
||||
|
||||
R = []
|
||||
if len( texts ) == 4 :
|
||||
for j in range( times ):
|
||||
|
||||
R.append(texts[0])
|
||||
|
||||
for i in range ( extra ):
|
||||
R.append(texts[1])
|
||||
|
||||
R.append(texts[2])
|
||||
for i in range ( extra ):
|
||||
R.append(texts[3])
|
||||
|
||||
return R
|
||||
|
||||
if len( texts ) == 1 :
|
||||
texts.append(texts[0])
|
||||
|
||||
R.append(texts[0])
|
||||
for i in range (times * (2 * (extra + 1)) - 1) :
|
||||
R.append(texts[1])
|
||||
return R
|
||||
|
||||
def genFirstWaveBrick (text, extra, times) :
|
||||
|
||||
pattern = {
|
||||
'p': ['pclk', '111', 'nclk', '000'],
|
||||
'n': ['nclk', '000', 'pclk', '111'],
|
||||
'P': ['Pclk', '111', 'nclk', '000'],
|
||||
'N': ['Nclk', '000', 'pclk', '111'],
|
||||
'l': ['000'],
|
||||
'L': ['000'],
|
||||
'0': ['000'],
|
||||
'h': ['111'],
|
||||
'H': ['111'],
|
||||
'1': ['111'],
|
||||
'=': ['vvv-2'],
|
||||
'2': ['vvv-2'],
|
||||
'3': ['vvv-3'],
|
||||
'4': ['vvv-4'],
|
||||
'5': ['vvv-5'],
|
||||
'd': ['ddd'],
|
||||
'u': ['uuu'],
|
||||
'z': ['zzz']
|
||||
}
|
||||
|
||||
return genBrick( pattern.get( text, ['xxx'] ) , extra, times );
|
||||
|
||||
def genWaveBrick (text, extra, times) :
|
||||
|
||||
x1 = {'p':'pclk', 'n':'nclk', 'P':'Pclk', 'N':'Nclk', 'h':'pclk', 'l':'nclk', 'H':'Pclk', 'L':'Nclk'}
|
||||
x2 = {'0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v' }
|
||||
x3 = {'0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
|
||||
y1 = {
|
||||
'p':'0', 'n':'1',
|
||||
'P':'0', 'N':'1',
|
||||
'h':'1', 'l':'0',
|
||||
'H':'1', 'L':'0',
|
||||
'0':'0', '1':'1', 'x':'x', 'd':'d', 'u':'u', 'z':'z', '=':'v', '2':'v', '3':'v', '4':'v', '5':'v'}
|
||||
|
||||
y2 = {
|
||||
'p': '', 'n': '',
|
||||
'P': '', 'N': '',
|
||||
'h': '', 'l': '',
|
||||
'H': '', 'L': '',
|
||||
'0': '', '1': '', 'x': '', 'd': '', 'u': '', 'z': '', '=':'-2', '2':'-2', '3':'-3', '4':'-4', '5':'-5'}
|
||||
|
||||
x4 = {
|
||||
'p': '111', 'n': '000',
|
||||
'P': '111', 'N': '000',
|
||||
'h': '111', 'l': '000',
|
||||
'H': '111', 'L': '000',
|
||||
'0': '000', '1': '111', 'x': 'xxx', 'd': 'ddd', 'u': 'uuu', 'z': 'zzz',
|
||||
'=': 'vvv-2', '2': 'vvv-2', '3': 'vvv-3', '4': 'vvv-4', '5': 'vvv-5'}
|
||||
|
||||
x5 = {'p':'nclk', 'n':'pclk', 'P':'nclk', 'N':'pclk'}
|
||||
x6 = {'p': '000', 'n': '111', 'P': '000', 'N': '111'}
|
||||
xclude = {'hp':'111', 'Hp':'111', 'ln': '000', 'Ln': '000', 'nh':'111', 'Nh':'111', 'pl': '000', 'Pl':'000'}
|
||||
|
||||
#atext = text.split()
|
||||
atext = text
|
||||
|
||||
tmp0 = x4.get(atext[1])
|
||||
tmp1 = x1.get(atext[1])
|
||||
if tmp1 == None :
|
||||
tmp2 = x2.get(atext[1])
|
||||
if tmp2 == None :
|
||||
# unknown
|
||||
return genBrick(['xxx'], extra, times)
|
||||
else :
|
||||
tmp3 = y1.get(atext[0])
|
||||
if tmp3 == None :
|
||||
# unknown
|
||||
return genBrick(['xxx'], extra, times)
|
||||
|
||||
# soft curves
|
||||
return genBrick([tmp3 + 'm' + tmp2 + y2[atext[0]] + x3[atext[1]], tmp0], extra, times)
|
||||
|
||||
else :
|
||||
tmp4 = xclude.get(text)
|
||||
if tmp4 != None :
|
||||
tmp1 = tmp4
|
||||
|
||||
# sharp curves
|
||||
tmp2 = x5.get(atext[1])
|
||||
if tmp2 == None :
|
||||
# hlHL
|
||||
return genBrick([tmp1, tmp0], extra, times)
|
||||
else :
|
||||
# pnPN
|
||||
return genBrick([tmp1, tmp0, tmp2, x6[atext[1]]], extra, times)
|
||||
|
||||
def parseWaveLane (text, extra) :
|
||||
|
||||
R = []
|
||||
Stack = text
|
||||
Next = Stack[0]
|
||||
Stack = Stack[1:]
|
||||
|
||||
Repeats = 1
|
||||
while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ): # repeaters parser
|
||||
Stack=Stack[1:]
|
||||
Repeats += 1
|
||||
|
||||
R.extend(genFirstWaveBrick(Next, extra, Repeats))
|
||||
|
||||
while len(Stack) :
|
||||
Top = Next
|
||||
Next = Stack[0]
|
||||
Stack = Stack[1:]
|
||||
Repeats = 1
|
||||
while len(Stack) and ( Stack[0] == '.' or Stack[0] == '|' ) : # repeaters parser
|
||||
Stack=Stack[1:]
|
||||
Repeats += 1
|
||||
R.extend(genWaveBrick((Top + Next), extra, Repeats))
|
||||
|
||||
for i in range( lane['phase'] ):
|
||||
R = R[1:]
|
||||
return R
|
||||
|
||||
def parseWaveLanes (sig) :
|
||||
|
||||
def data_extract (e) :
|
||||
tmp = e.get('data')
|
||||
if tmp == None : return None
|
||||
if is_type_str (tmp) : tmp=tmp.split()
|
||||
return tmp
|
||||
|
||||
content = []
|
||||
for sigx in sig :
|
||||
lane['period'] = sigx.get('period',1)
|
||||
lane['phase'] = int( sigx.get('phase',0 ) * 2 )
|
||||
sub_content=[]
|
||||
sub_content.append( [sigx.get('name',' '), sigx.get('phase',0 ) ] )
|
||||
sub_content.append( parseWaveLane( sigx['wave'], int(lane['period'] * lane['hscale'] - 1 ) ) if sigx.get('wave') else None )
|
||||
sub_content.append( data_extract(sigx) )
|
||||
content.append(sub_content)
|
||||
|
||||
return content
|
||||
|
||||
def findLaneMarkers (lanetext) :
|
||||
|
||||
lcount = 0
|
||||
gcount = 0
|
||||
ret = []
|
||||
for i in range( len( lanetext ) ) :
|
||||
if lanetext[i] == 'vvv-2' or lanetext[i] == 'vvv-3' or lanetext[i] == 'vvv-4' or lanetext[i] == 'vvv-5' :
|
||||
lcount += 1
|
||||
else :
|
||||
if lcount !=0 :
|
||||
ret.append(gcount - ((lcount + 1) / 2))
|
||||
lcount = 0
|
||||
|
||||
gcount += 1
|
||||
|
||||
if lcount != 0 :
|
||||
ret.append(gcount - ((lcount + 1) / 2))
|
||||
|
||||
return ret
|
||||
|
||||
def renderWaveLane (root, content, index) :
|
||||
|
||||
xmax = 0
|
||||
xgmax = 0
|
||||
glengths = []
|
||||
svgns = 'http://www.w3.org/2000/svg'
|
||||
xlinkns = 'http://www.w3.org/1999/xlink'
|
||||
xmlns = 'http://www.w3.org/XML/1998/namespace'
|
||||
for j in range( len(content) ):
|
||||
name = content[j][0][0]
|
||||
if name : # check name
|
||||
g = [
|
||||
'g',
|
||||
{
|
||||
'id': 'wavelane_' + str(j) + '_' + str(index),
|
||||
'transform': 'translate(0,' + str(lane['y0'] + j * lane['yo']) + ')'
|
||||
}
|
||||
]
|
||||
root.append(g)
|
||||
title = [
|
||||
'text',
|
||||
{
|
||||
'x': lane['tgo'],
|
||||
'y': lane['ym'],
|
||||
'class': 'info',
|
||||
'text-anchor': 'end',
|
||||
'xml:space': 'preserve'
|
||||
},
|
||||
['tspan', name]
|
||||
]
|
||||
g.append(title)
|
||||
|
||||
glengths.append( len(name) * font_width + font_width )
|
||||
|
||||
xoffset = content[j][0][1]
|
||||
xoffset = math.ceil(2 * xoffset) - 2 * xoffset if xoffset > 0 else -2 * xoffset
|
||||
gg = [
|
||||
'g',
|
||||
{
|
||||
'id': 'wavelane_draw_' + str(j) + '_' + str(index),
|
||||
'transform': 'translate(' + str( xoffset * lane['xs'] ) + ', 0)'
|
||||
}
|
||||
]
|
||||
g.append(gg)
|
||||
|
||||
if content[j][1] :
|
||||
for i in range( len(content[j][1]) ) :
|
||||
b = [
|
||||
'use',
|
||||
{
|
||||
#'id': 'use_' + str(i) + '_' + str(j) + '_' + str(index),
|
||||
'xmlns:xlink':xlinkns,
|
||||
'xlink:href': '#' + str( content[j][1][i] ),
|
||||
'transform': 'translate(' + str(i * lane['xs']) + ')'
|
||||
}
|
||||
]
|
||||
gg.append(b)
|
||||
|
||||
if content[j][2] and len(content[j][2]) :
|
||||
labels = findLaneMarkers(content[j][1])
|
||||
if len(labels) != 0 :
|
||||
for k in range( len(labels) ) :
|
||||
if content[j][2] and k < len(content[j][2]) :
|
||||
title = [
|
||||
'text',
|
||||
{
|
||||
'x': int(labels[k]) * lane['xs'] + lane['xlabel'],
|
||||
'y': lane['ym'],
|
||||
'text-anchor': 'middle',
|
||||
'xml:space': 'preserve'
|
||||
},
|
||||
['tspan',content[j][2][k]]
|
||||
]
|
||||
gg.append(title)
|
||||
|
||||
|
||||
if len(content[j][1]) > xmax :
|
||||
xmax = len(content[j][1])
|
||||
|
||||
lane['xmax'] = xmax
|
||||
lane['xg'] = xgmax + 20
|
||||
return glengths
|
||||
|
||||
def renderMarks (root, content, index) :
|
||||
|
||||
def captext ( g, cxt, anchor, y ) :
|
||||
|
||||
if cxt.get(anchor) and cxt[anchor].get('text') :
|
||||
tmark = [
|
||||
'text',
|
||||
{
|
||||
'x': float( cxt['xmax'] ) * float( cxt['xs'] ) / 2,
|
||||
'y': y,
|
||||
'text-anchor': 'middle',
|
||||
'fill': '#000',
|
||||
'xml:space': 'preserve'
|
||||
}, cxt[anchor]['text']
|
||||
]
|
||||
g.append(tmark)
|
||||
|
||||
def ticktock ( g, cxt, ref1, ref2, x, dx, y, length ) :
|
||||
L = []
|
||||
|
||||
if cxt.get(ref1) == None or cxt[ref1].get(ref2) == None :
|
||||
return
|
||||
|
||||
val = cxt[ref1][ref2]
|
||||
if is_type_str( val ) :
|
||||
val = val.split()
|
||||
elif type( val ) is int :
|
||||
offset = val
|
||||
val = []
|
||||
for i in range ( length ) :
|
||||
val.append(i + offset)
|
||||
|
||||
if type( val ) is list :
|
||||
if len( val ) == 0 :
|
||||
return
|
||||
elif len( val ) == 1 :
|
||||
offset = val[0]
|
||||
if is_type_str(offset) :
|
||||
L = val
|
||||
else :
|
||||
for i in range ( length ) :
|
||||
L[i] = i + offset
|
||||
|
||||
elif len( val ) == 2:
|
||||
offset = int(val[0])
|
||||
step = int(val[1])
|
||||
tmp = val[1].split('.')
|
||||
if len( tmp ) == 2 :
|
||||
dp = len( tmp[1] )
|
||||
|
||||
if is_type_str(offset) or is_type_str(step) :
|
||||
L = val
|
||||
else :
|
||||
offset = step * offset
|
||||
for i in range( length ) :
|
||||
L[i] = "{0:.",dp,"f}".format(step * i + offset)
|
||||
|
||||
else :
|
||||
L = val
|
||||
|
||||
else :
|
||||
return
|
||||
|
||||
for i in range( length ) :
|
||||
tmp = L[i]
|
||||
tmark = [
|
||||
'text',
|
||||
{
|
||||
'x': i * dx + x,
|
||||
'y': y,
|
||||
'text-anchor': 'middle',
|
||||
'class': 'muted',
|
||||
'xml:space': 'preserve'
|
||||
}, str(tmp)
|
||||
]
|
||||
g.append(tmark)
|
||||
|
||||
mstep = 2 * int(lane['hscale'])
|
||||
mmstep = mstep * lane['xs']
|
||||
marks = int( lane['xmax'] / mstep )
|
||||
gy = len( content ) * int(lane['yo'])
|
||||
|
||||
g = ['g', {'id': 'gmarks_' + str(index)}]
|
||||
root.insert(0,g)
|
||||
|
||||
for i in range( marks + 1):
|
||||
gg = [
|
||||
'path',
|
||||
{
|
||||
'id': 'gmark_' + str(i) + '_' + str(index),
|
||||
'd': 'm ' + str(i * mmstep) + ',' + '0' + ' 0,' + str(gy),
|
||||
'style': 'stroke:#888;stroke-width:0.5;stroke-dasharray:1,3'
|
||||
}
|
||||
]
|
||||
g.append( gg )
|
||||
|
||||
captext(g, lane, 'head', -33 if lane['yh0'] else -13 )
|
||||
captext(g, lane, 'foot', gy + ( 45 if lane['yf0'] else 25 ) )
|
||||
|
||||
ticktock( g, lane, 'head', 'tick', 0, mmstep, -5, marks + 1)
|
||||
ticktock( g, lane, 'head', 'tock', mmstep / 2, mmstep, -5, marks)
|
||||
ticktock( g, lane, 'foot', 'tick', 0, mmstep, gy + 15, marks + 1)
|
||||
ticktock( g, lane, 'foot', 'tock', mmstep / 2, mmstep, gy + 15, marks)
|
||||
|
||||
def renderArcs (root, source, index, top) :
|
||||
|
||||
Stack = []
|
||||
Edge = {'words': [], 'frm': 0, 'shape': '', 'to': 0, 'label': ''}
|
||||
Events = {}
|
||||
svgns = 'http://www.w3.org/2000/svg'
|
||||
xmlns = 'http://www.w3.org/XML/1998/namespace'
|
||||
|
||||
if source :
|
||||
for i in range (len (source) ) :
|
||||
lane['period'] = source[i].get('period',1)
|
||||
lane['phase'] = int( source[i].get('phase',0 ) * 2 )
|
||||
text = source[i].get('node')
|
||||
if text:
|
||||
Stack = text
|
||||
pos = 0
|
||||
while len( Stack ) :
|
||||
eventname = Stack[0]
|
||||
Stack=Stack[1:]
|
||||
if eventname != '.' :
|
||||
Events[eventname] = {
|
||||
'x' : str( int( float( lane['xs'] ) * (2 * pos * lane['period'] * lane['hscale'] - lane['phase'] ) + float( lane['xlabel'] ) ) ),
|
||||
'y' : str( int( i * lane['yo'] + lane['y0'] + float( lane['ys'] ) * 0.5 ) )
|
||||
}
|
||||
pos += 1
|
||||
|
||||
gg = [ 'g', { 'id' : 'wavearcs_' + str( index ) } ]
|
||||
root.append(gg)
|
||||
|
||||
if top.get('edge') :
|
||||
for i in range( len ( top['edge'] ) ) :
|
||||
Edge['words'] = top['edge'][i].split()
|
||||
Edge['label'] = top['edge'][i][len(Edge['words'][0]):]
|
||||
Edge['label'] = Edge['label'][1:]
|
||||
Edge['frm'] = Edge['words'][0][0]
|
||||
Edge['to'] = Edge['words'][0][-1]
|
||||
Edge['shape'] = Edge['words'][0][1:-1]
|
||||
frm = Events[Edge['frm']]
|
||||
to = Events[Edge['to']]
|
||||
gmark = [
|
||||
'path',
|
||||
{
|
||||
'id': 'gmark_' + Edge['frm'] + '_' + Edge['to'],
|
||||
'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + to['x'] + ',' + to['y'],
|
||||
'style': 'fill:none;stroke:#00F;stroke-width:1'
|
||||
}
|
||||
]
|
||||
gg.append(gmark)
|
||||
dx = float( to['x'] ) - float( frm['x'] )
|
||||
dy = float( to['y'] ) - float( frm['y'] )
|
||||
lx = (float(frm['x']) + float(to['x'])) / 2
|
||||
ly = (float(frm['y']) + float(to['y'])) / 2
|
||||
pattern = {
|
||||
'~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
|
||||
'-~' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
|
||||
'~-' : {'d': 'M ' + frm['x'] + ',' + frm['y'] + ' c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy) },
|
||||
'-|' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
|
||||
'|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
|
||||
'-|-' : {'d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
|
||||
'->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none'},
|
||||
'~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
|
||||
'-~>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
|
||||
'~->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + '0' + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
|
||||
'-|>' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
|
||||
'|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' 0,' + str(dy) + ' ' + str(dx) + ',0'},
|
||||
'-|->' : {'style': 'marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none', 'd': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'},
|
||||
'<->' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none'},
|
||||
'<~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
|
||||
'<-~>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'M ' + frm['x'] + ',' + frm['y'] + ' ' + 'c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + str(dy) + ' ' + str(dx) + ', ' + str(dy)},
|
||||
'<-|>' : {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx) + ',0 0,' + str(dy)},
|
||||
'<-|->': {'style': 'marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none','d': 'm ' + frm['x'] + ',' + frm['y'] + ' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str(dx / 2) + ',0'}
|
||||
}
|
||||
gmark[1].update( pattern.get( Edge['shape'], { 'style': 'fill:none;stroke:#00F;stroke-width:1' } ) )
|
||||
|
||||
if Edge['label']:
|
||||
if Edge['shape'] == '-~' :
|
||||
lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
|
||||
if Edge['shape'] == '~-' :
|
||||
lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
|
||||
if Edge['shape'] == '-|' :
|
||||
lx = float(to['x'])
|
||||
if Edge['shape'] == '|-' :
|
||||
lx = float(frm['x'])
|
||||
if Edge['shape'] == '-~>':
|
||||
lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
|
||||
if Edge['shape'] == '~->':
|
||||
lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.25
|
||||
if Edge['shape'] == '-|>' :
|
||||
lx = float(to['x'])
|
||||
if Edge['shape'] == '|->' :
|
||||
lx = float(frm['x'])
|
||||
if Edge['shape'] == '<-~>':
|
||||
lx = float(frm['x']) + (float(to['x']) - float(frm['x'])) * 0.75
|
||||
if Edge['shape'] =='<-|>' :
|
||||
lx = float(to['x'])
|
||||
|
||||
lwidth = len( Edge['label'] ) * font_width
|
||||
label = [
|
||||
'text',
|
||||
{
|
||||
'style': 'font-size:10px;',
|
||||
'text-anchor': 'middle',
|
||||
'xml:space': 'preserve',
|
||||
'x': int( lx ),
|
||||
'y': int( ly + 3 )
|
||||
},
|
||||
[ 'tspan', Edge['label'] ]
|
||||
]
|
||||
underlabel = [
|
||||
'rect',
|
||||
{
|
||||
'height': 9,
|
||||
'style': 'fill:#FFF;',
|
||||
'width': lwidth,
|
||||
'x': int( lx - lwidth / 2 ),
|
||||
'y': int( ly - 5 )
|
||||
}
|
||||
]
|
||||
gg.append(underlabel)
|
||||
gg.append(label)
|
||||
|
||||
for k in Events:
|
||||
if k.islower() :
|
||||
if int( Events[k]['x'] ) > 0 :
|
||||
lwidth = len( k ) * font_width
|
||||
underlabel = [
|
||||
'rect',
|
||||
{
|
||||
'x': float( Events[k]['x'] ) - float(lwidth) / 2,
|
||||
'y': int( Events[k]['y'] ) - 4,
|
||||
'height': 8,
|
||||
'width': lwidth,
|
||||
'style': 'fill:#FFF;'
|
||||
}
|
||||
]
|
||||
gg.append(underlabel)
|
||||
label = [
|
||||
'text',
|
||||
{
|
||||
'style': 'font-size:8px;',
|
||||
'x': int( Events[k]['x'] ),
|
||||
'y': int( Events[k]['y'] ) + 2,
|
||||
'width': lwidth,
|
||||
'text-anchor': 'middle'
|
||||
},
|
||||
k
|
||||
]
|
||||
gg.append(label)
|
||||
|
||||
def parseConfig (source) :
|
||||
|
||||
lane['hscale'] = 1
|
||||
if lane.get('hscale0') :
|
||||
lane['hscale'] = lane['hscale0']
|
||||
|
||||
if source and source.get('config') and source.get('config').get('hscale'):
|
||||
hscale = round(source.get('config').get('hscale'))
|
||||
if hscale > 0 :
|
||||
if hscale > 100 : hscale = 100
|
||||
lane['hscale'] = hscale
|
||||
|
||||
lane['yh0'] = 0
|
||||
lane['yh1'] = 0
|
||||
if source and source.get('head') :
|
||||
lane['head'] = source['head']
|
||||
if source.get('head').get('tick',0) == 0 : lane['yh0'] = 20
|
||||
if source.get('head').get('tock',0) == 0 : lane['yh0'] = 20
|
||||
if source.get('head').get('text') : lane['yh1'] = 46; lane['head']['text'] = source['head']['text']
|
||||
|
||||
lane['yf0'] = 0
|
||||
lane['yf1'] = 0
|
||||
if source and source.get('foot') :
|
||||
lane['foot'] = source['foot']
|
||||
if source.get('foot').get('tick',0) == 0 : lane['yf0'] = 20
|
||||
if source.get('foot').get('tock',0) == 0 : lane['yf0'] = 20
|
||||
if source.get('foot').get('text') : lane['yf1'] = 46; lane['foot']['text'] = source['foot']['text']
|
||||
|
||||
def rec (tmp, state) :
|
||||
|
||||
name = str( tmp[0] )
|
||||
delta_x = 25
|
||||
|
||||
state['x'] += delta_x
|
||||
for i in range( len( tmp ) ) :
|
||||
if type( tmp[i] ) is list :
|
||||
old_y = state['y']
|
||||
rec( tmp[i], state )
|
||||
state['groups'].append( {'x':state['xx'], 'y':old_y, 'height':state['y'] - old_y, 'name': state['name'] } )
|
||||
elif type( tmp[i] ) is dict :
|
||||
state['lanes'].append(tmp[i])
|
||||
state['width'].append(state['x'])
|
||||
state['y'] += 1
|
||||
|
||||
state['xx'] = state['x']
|
||||
state['x'] -= delta_x
|
||||
state['name'] = name
|
||||
|
||||
def insertSVGTemplate (index, parent, source) :
|
||||
|
||||
e = waveskin.WaveSkin['default']
|
||||
|
||||
if source.get('config') and source.get('config').get('skin') :
|
||||
if waveskin.WaveSkin.get( source.get('config').get('skin') ) :
|
||||
e = waveskin.WaveSkin[ source.get('config').get('skin') ]
|
||||
|
||||
if index == 0 :
|
||||
lane['xs'] = int( e[3][1][2][1]['width'] )
|
||||
lane['ys'] = int( e[3][1][2][1]['height'] )
|
||||
lane['xlabel'] = int( e[3][1][2][1]['x'] )
|
||||
lane['ym'] = int( e[3][1][2][1]['y'] )
|
||||
|
||||
else :
|
||||
e = ['svg', {'id': 'svg', 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:xlink': 'http://www.w3.org/1999/xlink', 'height': '0'},
|
||||
['g', {'id': 'waves'},
|
||||
['g', {'id': 'lanes'}],
|
||||
['g', {'id': 'groups'}]
|
||||
]
|
||||
]
|
||||
|
||||
e[-1][1]['id'] = 'waves_' + str(index)
|
||||
e[-1][2][1]['id'] = 'lanes_' + str(index)
|
||||
e[-1][3][1]['id'] = 'groups_' + str(index)
|
||||
e[1]['id'] = 'svgcontent_' + str(index)
|
||||
e[1]['height'] = 0
|
||||
|
||||
parent.extend(e)
|
||||
|
||||
def renderWaveForm (index, source, output) :
|
||||
|
||||
xmax = 0
|
||||
root = []
|
||||
groups = []
|
||||
|
||||
if source.get('signal'):
|
||||
insertSVGTemplate(index, output, source)
|
||||
parseConfig( source )
|
||||
ret = {'x':0, 'y':0, 'xmax':0, 'width':[], 'lanes':[], 'groups':[] }
|
||||
rec( source['signal'], ret )
|
||||
content = parseWaveLanes(ret['lanes'])
|
||||
glengths = renderWaveLane(root, content, index)
|
||||
for i in range( len( glengths ) ):
|
||||
xmax = max( xmax, ( glengths[i] + ret['width'][i] ) )
|
||||
renderMarks(root, content, index)
|
||||
renderArcs(root, ret['lanes'], index, source)
|
||||
renderGaps(root, ret['lanes'], index)
|
||||
renderGroups(groups, ret['groups'], index)
|
||||
lane['xg'] = int( math.ceil( float( xmax - lane['tgo'] ) / float(lane['xs'] ) ) ) * lane['xs']
|
||||
width = (lane['xg'] + lane['xs'] * (lane['xmax'] + 1) )
|
||||
height = len(content) * lane['yo'] + lane['yh0'] + lane['yh1'] + lane['yf0'] + lane['yf1']
|
||||
output[1]={
|
||||
'id' :'svgcontent_' + str(index),
|
||||
'xmlns' :"http://www.w3.org/2000/svg",
|
||||
'xmlns:xlink':"http://www.w3.org/1999/xlink",
|
||||
'width' :str(width),
|
||||
'height' :str(height),
|
||||
'viewBox' :'0 0 ' + str(width) + ' ' + str(height),
|
||||
'overflow' :"hidden"
|
||||
}
|
||||
output[-1][2][1]['transform']='translate(' + str(lane['xg'] + 0.5) + ', ' + str((float(lane['yh0']) + float(lane['yh1'])) + 0.5) + ')'
|
||||
|
||||
output[-1][2].extend(root)
|
||||
output[-1][3].extend(groups)
|
||||
|
||||
def renderGroups (root, groups, index) :
|
||||
|
||||
svgns = 'http://www.w3.org/2000/svg',
|
||||
xmlns = 'http://www.w3.org/XML/1998/namespace'
|
||||
|
||||
for i in range( len( groups ) ) :
|
||||
group = [
|
||||
'path',
|
||||
{
|
||||
'id': 'group_' + str(i) + '_' + str(index),
|
||||
'd': 'm ' + str( groups[i]['x'] + 0.5 ) + ',' + str( groups[i]['y']* lane['yo'] + 3.5 + lane['yh0'] + lane['yh1'] ) + ' c -3,0 -5,2 -5,5 l 0,' + str( int( groups[i]['height'] * lane['yo'] - 16 ) ) + ' c 0,3 2,5 5,5',
|
||||
'style': 'stroke:#0041c4;stroke-width:1;fill:none'
|
||||
}
|
||||
]
|
||||
root.append(group)
|
||||
|
||||
name = groups[i]['name']
|
||||
x = str( int( groups[i]['x'] - 10 ) )
|
||||
y = str( int( lane['yo'] * (groups[i]['y'] + (float(groups[i]['height']) / 2)) + lane['yh0'] + lane['yh1'] ) )
|
||||
label = [
|
||||
['g',
|
||||
{'transform': 'translate(' + x + ',' + y + ')'},
|
||||
['g', {'transform': 'rotate(270)'},
|
||||
'text',
|
||||
{
|
||||
'text-anchor': 'middle',
|
||||
'class': 'info',
|
||||
'xml:space' : 'preserve'
|
||||
},
|
||||
['tspan',name]
|
||||
]
|
||||
]
|
||||
]
|
||||
root.append(label)
|
||||
|
||||
def renderGaps (root, source, index) :
|
||||
|
||||
Stack = []
|
||||
svgns = 'http://www.w3.org/2000/svg',
|
||||
xlinkns = 'http://www.w3.org/1999/xlink'
|
||||
|
||||
if source:
|
||||
|
||||
gg = [
|
||||
'g',
|
||||
{ 'id': 'wavegaps_' + str(index) }
|
||||
]
|
||||
|
||||
for i in range( len( source )):
|
||||
lane['period'] = source[i].get('period',1)
|
||||
lane['phase'] = int( source[i].get('phase',0 ) * 2 )
|
||||
|
||||
g = [
|
||||
'g',
|
||||
{
|
||||
'id': 'wavegap_' + str(i) + '_' + str(index),
|
||||
'transform': 'translate(0,' + str(lane['y0'] + i * lane['yo']) + ')'
|
||||
}
|
||||
]
|
||||
gg.append(g)
|
||||
|
||||
if source[i].get('wave'):
|
||||
text = source[i]['wave']
|
||||
Stack = text
|
||||
pos = 0
|
||||
while len( Stack ) :
|
||||
c = Stack [0]
|
||||
Stack = Stack[1:]
|
||||
if c == '|' :
|
||||
b = [
|
||||
'use',
|
||||
{
|
||||
'xmlns:xlink':xlinkns,
|
||||
'xlink:href':'#gap',
|
||||
'transform': 'translate(' + str(int(float(lane['xs']) * ((2 * pos + 1) * float(lane['period']) * float(lane['hscale']) - float(lane['phase'])))) + ')'
|
||||
}
|
||||
]
|
||||
g.append(b)
|
||||
pos += 1
|
||||
|
||||
root.append( gg )
|
||||
|
||||
def is_type_str( var ) :
|
||||
if sys.version_info[0] < 3:
|
||||
return type( var ) is str or type( var ) is unicode
|
||||
else:
|
||||
return type( var ) is str
|
||||
|
||||
def convert_to_svg( root ) :
|
||||
|
||||
svg_output = ''
|
||||
|
||||
if type( root ) is list:
|
||||
if len(root) >= 2 and type( root[1] ) is dict:
|
||||
if len( root ) == 2 :
|
||||
svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '/>\n'
|
||||
elif len( root ) >= 3 :
|
||||
svg_output += '<' + root[0] + convert_to_svg( root[1] ) + '>\n'
|
||||
if len( root ) == 3:
|
||||
svg_output += convert_to_svg( root[2] )
|
||||
else:
|
||||
svg_output += convert_to_svg( root[2:] )
|
||||
svg_output += '</' + root[0] + '>\n'
|
||||
elif type( root[0] ) is list:
|
||||
for eleml in root:
|
||||
svg_output += convert_to_svg( eleml )
|
||||
else:
|
||||
svg_output += '<' + root[0] + '>\n'
|
||||
for eleml in root[1:]:
|
||||
svg_output += convert_to_svg( eleml )
|
||||
svg_output += '</' + root[0] + '>\n'
|
||||
elif type( root ) is dict:
|
||||
for elemd in root :
|
||||
svg_output += ' ' + elemd + '="' + str(root[elemd]) + '"'
|
||||
else:
|
||||
svg_output += root
|
||||
|
||||
return svg_output
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if len( sys.argv ) != 5:
|
||||
print ( 'Usage : ' + sys.argv[0] + ' source <input.json> svg <output.svg>' )
|
||||
exit(1)
|
||||
|
||||
if sys.argv[3] != 'svg' :
|
||||
print ( 'Error: only SVG format supported.' )
|
||||
exit(1)
|
||||
|
||||
output=[]
|
||||
inputfile = sys.argv[2]
|
||||
outputfile = sys.argv[4]
|
||||
|
||||
with open(inputfile,'r') as f:
|
||||
jinput = json.load(f)
|
||||
|
||||
renderWaveForm(0,jinput,output)
|
||||
svg_output = convert_to_svg(output)
|
||||
|
||||
with open(outputfile,'w') as f:
|
||||
f.write( svg_output )
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import debug
|
||||
from drc_value import *
|
||||
from drc_lut import *
|
||||
|
||||
class design_rules():
|
||||
"""
|
||||
This is a class that implements the design rules structures.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
self.tech_name = name
|
||||
self.rules = {}
|
||||
|
||||
def add(self, name, value):
|
||||
self.rules[name] = value
|
||||
|
||||
def __call__(self, name, *args):
|
||||
rule = self.rules[name]
|
||||
if callable(rule):
|
||||
return rule(*args)
|
||||
else:
|
||||
return rule
|
||||
|
||||
def __setitem__(self, b, c):
|
||||
"""
|
||||
For backward compatibility with existing rules.
|
||||
"""
|
||||
self.rules[b] = c
|
||||
|
||||
def __getitem__(self, b):
|
||||
"""
|
||||
For backward compatibility with existing rules.
|
||||
"""
|
||||
rule = self.rules[b]
|
||||
if not callable(rule):
|
||||
return rule
|
||||
else:
|
||||
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import debug
|
||||
|
||||
class drc_lut():
|
||||
"""
|
||||
Implement a lookup table of rules.
|
||||
Each element is a tuple with the last value being the rule.
|
||||
It searches through backwards until all of the key values are
|
||||
met and returns the rule value.
|
||||
For exampe, the key values can be width and length,
|
||||
and it would return the rule for a wire of at least a given width and length.
|
||||
A dimension can be ignored by passing inf.
|
||||
"""
|
||||
def __init__(self, table):
|
||||
self.table = table
|
||||
|
||||
def __call__(self, *key):
|
||||
"""
|
||||
Lookup a given tuple in the table.
|
||||
"""
|
||||
if len(key)==0:
|
||||
first_key = list(sorted(self.table.keys()))[0]
|
||||
return self.table[first_key]
|
||||
|
||||
for table_key in sorted(self.table.keys(), reverse=True):
|
||||
if self.match(key, table_key):
|
||||
return self.table[table_key]
|
||||
|
||||
|
||||
def match(self, key1, key2):
|
||||
"""
|
||||
Determine if key1>=key2 for all tuple pairs.
|
||||
(i.e. return false if key1<key2 for any pair.)
|
||||
"""
|
||||
# If any one pair is less than, return False
|
||||
debug.check(len(key1)==len(key2),"Comparing invalid key lengths.")
|
||||
for k1,k2 in zip(key1,key2):
|
||||
if k1 < k2:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
class drc_value():
|
||||
"""
|
||||
A single DRC value.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __call__(self, *args):
|
||||
"""
|
||||
Return the value.
|
||||
"""
|
||||
return self.value
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
tech_name = "freepdk45"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [1.0]
|
||||
temperatures = [25]
|
||||
|
||||
output_path = "temp"
|
||||
output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name)
|
||||
|
||||
#Below are some additions to test additional ports on sram
|
||||
#bitcell = "pbitcell"
|
||||
|
||||
# These are the configuration parameters
|
||||
#rw_ports = 2
|
||||
#r_ports = 2
|
||||
#w_ports = 2
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [ 5.0 ]
|
||||
temperatures = [ 25 ]
|
||||
|
||||
output_path = "temp"
|
||||
output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
word_size = 8
|
||||
num_words = 128
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [ 5.0 ]
|
||||
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"
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
bitcell = "bitcell_1rw_1r"
|
||||
replica_bitcell = "replica_bitcell_1rw_1r"
|
||||
num_rw_ports = 1
|
||||
num_r_ports = 1
|
||||
num_w_ports = 0
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
temperatures = [25]
|
||||
|
||||
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,11 @@
|
|||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
tech_name = "freepdk45"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [1.0]
|
||||
temperatures = [25]
|
||||
|
||||
output_path = "temp"
|
||||
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [5.0]
|
||||
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"
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
word_size = 16
|
||||
num_words = 256
|
||||
|
||||
tech_name = "scn4m_subm"
|
||||
process_corners = ["TT"]
|
||||
supply_voltages = [ 3.3 ]
|
||||
temperatures = [ 25 ]
|
||||
|
||||
output_path = "temp"
|
||||
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
|
||||
|
||||
drc_name = "magic"
|
||||
lvs_name = "netgen"
|
||||
pex_name = "magic"
|
||||
|
|
@ -60,6 +60,11 @@ class VlsiLayout:
|
|||
self.tempCoordinates=None
|
||||
self.tempPassFail = True
|
||||
|
||||
# This is a dict indexed by the pin labels.
|
||||
# It contains a list of list of shapes, one for each occurance of the label.
|
||||
# Multiple labels may be disconnected.
|
||||
self.pins = {}
|
||||
|
||||
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
|
||||
#helper method to rotate a list of coordinates
|
||||
angle=math.radians(float(0))
|
||||
|
|
@ -143,22 +148,22 @@ class VlsiLayout:
|
|||
structureNames=[]
|
||||
for name in self.structures:
|
||||
structureNames.append(name)
|
||||
|
||||
for name in self.structures:
|
||||
if(len(self.structures[name].srefs)>0): #does this structure reference any others?
|
||||
for sref in self.structures[name].srefs: #go through each reference
|
||||
if sref.sName in structureNames: #and compare to our list
|
||||
structureNames.remove(sref.sName)
|
||||
|
||||
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,
|
||||
transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)):
|
||||
#since this is a recursive function, must deal with the default
|
||||
#parameters explicitly
|
||||
#parameters explicitly
|
||||
if startingStructureName == None:
|
||||
startingStructureName = self.rootStructureName
|
||||
|
||||
|
||||
#set up the rotation matrix
|
||||
if(rotateAngle == None or rotateAngle == ""):
|
||||
angle = 0
|
||||
|
|
@ -206,7 +211,11 @@ class VlsiLayout:
|
|||
def initialize(self):
|
||||
self.deduceHierarchy()
|
||||
#self.traverseTheHierarchy()
|
||||
self.populateCoordinateMap()
|
||||
self.populateCoordinateMap()
|
||||
|
||||
for layerNumber in self.layerNumbersInUse:
|
||||
self.processLabelPins(layerNumber)
|
||||
|
||||
|
||||
def populateCoordinateMap(self):
|
||||
def addToXyTree(startingStructureName = None,transformPath = None):
|
||||
|
|
@ -295,6 +304,7 @@ class VlsiLayout:
|
|||
debug.info(1,"DEBUG: Structure %s Found"%StructureName)
|
||||
StructureFound = True
|
||||
|
||||
debug.check(StructureFound,"Could not find layout to instantiate {}".format(StructureName))
|
||||
|
||||
|
||||
# If layoutToAdd is a unique object (not this), then copy hierarchy,
|
||||
|
|
@ -478,6 +488,10 @@ class VlsiLayout:
|
|||
return False #these shapes are ok
|
||||
|
||||
def isPointInsideOfBox(self,pointCoordinates,boxCoordinates):
|
||||
"""
|
||||
Check if a point is contained in the shape
|
||||
"""
|
||||
debug.check(len(boxCoordinates)==4,"Invalid number of coordinates for box.")
|
||||
leftBound = boxCoordinates[0][0]
|
||||
rightBound = boxCoordinates[0][0]
|
||||
topBound = boxCoordinates[0][1]
|
||||
|
|
@ -499,7 +513,9 @@ class VlsiLayout:
|
|||
return True
|
||||
|
||||
def isShapeInsideOfBox(self,shapeCoordinates, boxCoordinates):
|
||||
#go through every point in the shape to test if they are all inside the box
|
||||
"""
|
||||
Go through every point in the shape to test if they are all inside the box.
|
||||
"""
|
||||
for point in shapeCoordinates:
|
||||
if not self.isPointInsideOfBox(point,boxCoordinates):
|
||||
return False
|
||||
|
|
@ -619,7 +635,8 @@ class VlsiLayout:
|
|||
|
||||
def updateBoundary(self,thisBoundary,cellBoundary):
|
||||
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
|
||||
if cellBoundary==[None,None,None,None]:
|
||||
# If any are None
|
||||
if not (cellBoundary[0] and cellBoundary[1] and cellBoundary[2] and cellBoundary[3]):
|
||||
cellBoundary=thisBoundary
|
||||
else:
|
||||
if cellBoundary[0]>left_bott_X:
|
||||
|
|
@ -633,160 +650,89 @@ class VlsiLayout:
|
|||
return cellBoundary
|
||||
|
||||
|
||||
def getLabelDBInfo(self,label_name):
|
||||
def getTexts(self, layer):
|
||||
"""
|
||||
Return the coordinates in DB units and layer of all matching labels
|
||||
Get all of the labels on a given layer only at the root level.
|
||||
"""
|
||||
label_list = []
|
||||
label_layer = None
|
||||
label_coordinate = [None, None]
|
||||
|
||||
# Why must this be the last one found? It breaks if we return the first.
|
||||
text_list = []
|
||||
for Text in self.structures[self.rootStructureName].texts:
|
||||
if Text.textString == label_name or Text.textString == label_name+"\x00":
|
||||
label_layer = Text.drawingLayer
|
||||
label_coordinate = Text.coordinates[0]
|
||||
if label_layer!=None:
|
||||
label_list.append((label_coordinate,label_layer))
|
||||
|
||||
debug.check(len(label_list)>0,"Did not find labels {0}.".format(label_name))
|
||||
return label_list
|
||||
|
||||
|
||||
def getLabelInfo(self,label_name):
|
||||
"""
|
||||
Return the coordinates in USER units and layer of a label
|
||||
"""
|
||||
label_list=self.getLabelDBInfo(label_name)
|
||||
new_list=[]
|
||||
for label in label_list:
|
||||
(label_coordinate,label_layer)=label
|
||||
user_coordinates = [x*self.units[0] for x in label_coordinate]
|
||||
new_list.append(user_coordinates,label_layer)
|
||||
return new_list
|
||||
if Text.drawingLayer == layer:
|
||||
text_list.append(Text)
|
||||
return text_list
|
||||
|
||||
def getPinShapeByLocLayer(self, coordinate, layer):
|
||||
"""
|
||||
Return the largest enclosing rectangle on a layer and at a location.
|
||||
Coordinates should be in USER units.
|
||||
"""
|
||||
db_coordinate = [x/self.units[0] for x in coordinate]
|
||||
return self.getPinShapeByDBLocLayer(db_coordinate, layer)
|
||||
|
||||
def getPinShapeByDBLocLayer(self, coordinate, layer):
|
||||
"""
|
||||
Return the largest enclosing rectangle on a layer and at a location.
|
||||
Coordinates should be in DB units.
|
||||
"""
|
||||
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
|
||||
|
||||
if len(pin_boundaries) == 0:
|
||||
debug.warning("Did not find pin on layer {0} at coordinate {1}".format(layer, coordinate))
|
||||
|
||||
# sort the boundaries, return the max area pin boundary
|
||||
pin_boundaries.sort(key=boundaryArea,reverse=True)
|
||||
pin_boundary=pin_boundaries[0]
|
||||
|
||||
# Convert to USER units
|
||||
pin_boundary=[pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
|
||||
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]
|
||||
|
||||
# Make a name if we don't have the pin name
|
||||
return ["p"+str(coordinate)+"_"+str(layer), layer, pin_boundary]
|
||||
|
||||
def getAllPinShapesByLocLayer(self, coordinate, layer):
|
||||
"""
|
||||
Return ALL the enclosing rectangles on the same layer
|
||||
at the given coordinate. Coordinates should be in USER units.
|
||||
"""
|
||||
db_coordinate = [int(x/self.units[0]) for x in coordinate]
|
||||
return self.getAllPinShapesByDBLocLayer(db_coordinate, layer)
|
||||
|
||||
def getAllPinShapesByDBLocLayer(self, coordinate, layer):
|
||||
"""
|
||||
Return ALL the enclosing rectangles on the same layer
|
||||
at the given coordinate. Input coordinates should be in DB units.
|
||||
Returns user unit shapes.
|
||||
"""
|
||||
pin_boundaries=self.getAllPinShapesInStructureList(coordinate, layer)
|
||||
|
||||
# Convert to user units
|
||||
new_boundaries = []
|
||||
for pin_boundary in pin_boundaries:
|
||||
new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
|
||||
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]])
|
||||
|
||||
return new_boundaries
|
||||
|
||||
def getPinShapeByLabel(self,label_name):
|
||||
def getPinShape(self, pin_name):
|
||||
"""
|
||||
Search for a pin label and return the largest enclosing rectangle
|
||||
on the same layer as the pin label.
|
||||
If there are multiple pin lists, return the max of each.
|
||||
"""
|
||||
label_list=self.getLabelDBInfo(label_name)
|
||||
shape_list=[]
|
||||
for label in label_list:
|
||||
(label_coordinate,label_layer)=label
|
||||
shape = self.getPinShapeByDBLocLayer(label_coordinate, label_layer)
|
||||
shape_list.append(shape)
|
||||
return shape_list
|
||||
pin_map = self.pins[pin_name]
|
||||
max_pins = []
|
||||
for pin_list in pin_map:
|
||||
max_pin = None
|
||||
max_area = 0
|
||||
for pin in pin_list:
|
||||
(layer,boundary) = pin
|
||||
new_area = boundaryArea(boundary)
|
||||
if max_pin == None or new_area>max_area:
|
||||
max_pin = pin
|
||||
max_area = new_area
|
||||
max_pins.append(max_pin)
|
||||
|
||||
def getAllPinShapesByLabel(self,label_name):
|
||||
return max_pins
|
||||
|
||||
|
||||
def getAllPinShapes(self, pin_name):
|
||||
"""
|
||||
Search for a pin label and return ALL the enclosing rectangles on the same layer
|
||||
as the pin label.
|
||||
"""
|
||||
|
||||
label_list=self.getLabelDBInfo(label_name)
|
||||
shape_list=[]
|
||||
for label in label_list:
|
||||
(label_coordinate,label_layer)=label
|
||||
shape_list.extend(self.getAllPinShapesByDBLocLayer(label_coordinate, label_layer))
|
||||
shape_list = []
|
||||
pin_map = self.pins[pin_name]
|
||||
for pin_list in pin_map:
|
||||
for pin in pin_list:
|
||||
(pin_layer, boundary) = pin
|
||||
shape_list.append(pin)
|
||||
|
||||
return shape_list
|
||||
|
||||
def getAllPinShapesInStructureList(self,coordinates,layer):
|
||||
|
||||
|
||||
def processLabelPins(self, layer):
|
||||
"""
|
||||
Given a coordinate, search for enclosing structures on the given layer.
|
||||
Return all pin shapes.
|
||||
Find all text labels and create a map to a list of shapes that
|
||||
they enclose on the given layer.
|
||||
"""
|
||||
boundaries = []
|
||||
for TreeUnit in self.xyTree:
|
||||
boundaries.extend(self.getPinInStructure(coordinates,layer,TreeUnit))
|
||||
# Get the labels on a layer in the root level
|
||||
labels = self.getTexts(layer)
|
||||
# Get all of the shapes on the layer at all levels
|
||||
# and transform them to the current level
|
||||
shapes = self.getAllShapes(layer)
|
||||
|
||||
return boundaries
|
||||
for label in labels:
|
||||
label_coordinate = label.coordinates[0]
|
||||
user_coordinate = [x*self.units[0] for x in label_coordinate]
|
||||
pin_shapes = []
|
||||
for boundary in shapes:
|
||||
if self.labelInRectangle(user_coordinate,boundary):
|
||||
pin_shapes.append((layer, boundary))
|
||||
|
||||
label_text = label.textString
|
||||
# Remove the padding if it exists
|
||||
if label_text[-1] == "\x00":
|
||||
label_text = label_text[0:-1]
|
||||
|
||||
def getPinInStructure(self,coordinates,layer,structure):
|
||||
"""
|
||||
Go through all the shapes in a structure and return the list of shapes
|
||||
that the label coordinates are inside.
|
||||
try:
|
||||
self.pins[label_text]
|
||||
except KeyError:
|
||||
self.pins[label_text] = []
|
||||
self.pins[label_text].append(pin_shapes)
|
||||
|
||||
|
||||
|
||||
def getAllShapes(self,layer):
|
||||
"""
|
||||
|
||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||
boundaries = []
|
||||
for boundary in self.structures[str(structureName)].boundaries:
|
||||
# Pin enclosures only work on rectangular pins so ignore any non rectangle
|
||||
# This may report not finding pins, but the user should fix this by adding a rectangle.
|
||||
if len(boundary.coordinates)!=5:
|
||||
continue
|
||||
if layer==boundary.drawingLayer:
|
||||
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=self.transformRectangle(boundaryRect,structureuVector,structurevVector)
|
||||
boundaryRect=[boundaryRect[0]+structureOrigin[0].item(),boundaryRect[1]+structureOrigin[1].item(),
|
||||
boundaryRect[2]+structureOrigin[0].item(),boundaryRect[3]+structureOrigin[1].item()]
|
||||
|
||||
if self.labelInRectangle(coordinates,boundaryRect):
|
||||
boundaries.append(boundaryRect)
|
||||
|
||||
return boundaries
|
||||
|
||||
|
||||
def getAllShapesInStructureList(self,layer):
|
||||
"""
|
||||
Return all pin shapes on a given layer.
|
||||
Return all gshapes on a given layer in [llx, lly, urx, ury] format and
|
||||
user units.
|
||||
"""
|
||||
boundaries = []
|
||||
for TreeUnit in self.xyTree:
|
||||
|
|
@ -811,7 +757,8 @@ class VlsiLayout:
|
|||
|
||||
def getShapesInStructure(self,layer,structure):
|
||||
"""
|
||||
Go through all the shapes in a structure and return the list of shapes.
|
||||
Go through all the shapes in a structure and return the list of shapes in
|
||||
the form [llx, lly, urx, ury]
|
||||
"""
|
||||
|
||||
(structureName,structureOrigin,structureuVector,structurevVector)=structure
|
||||
|
|
@ -819,6 +766,7 @@ class VlsiLayout:
|
|||
boundaries = []
|
||||
for boundary in self.structures[str(structureName)].boundaries:
|
||||
# FIXME: Right now, this only supports rectangular shapes!
|
||||
#debug.check(len(boundary.coordinates)==5,"Non-rectangular shape.")
|
||||
if len(boundary.coordinates)!=5:
|
||||
continue
|
||||
if layer==boundary.drawingLayer:
|
||||
|
|
@ -873,8 +821,8 @@ class VlsiLayout:
|
|||
"""
|
||||
Checks if a coordinate is within a given rectangle. Rectangle is [leftx, bottomy, rightx, topy].
|
||||
"""
|
||||
coordinate_In_Rectangle_x_range=(coordinate[0]>=int(rectangle[0]))&(coordinate[0]<=int(rectangle[2]))
|
||||
coordinate_In_Rectangle_y_range=(coordinate[1]>=int(rectangle[1]))&(coordinate[1]<=int(rectangle[3]))
|
||||
coordinate_In_Rectangle_x_range=(coordinate[0]>=rectangle[0])&(coordinate[0]<=rectangle[2])
|
||||
coordinate_In_Rectangle_y_range=(coordinate[1]>=rectangle[1])&(coordinate[1]<=rectangle[3])
|
||||
if coordinate_In_Rectangle_x_range & coordinate_In_Rectangle_y_range:
|
||||
return True
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
468eb9a4a038201c2b0004fe6e4ae9b2d37fdd57
|
||||
|
|
@ -24,14 +24,16 @@ def parse_args():
|
|||
global OPTS
|
||||
|
||||
option_list = {
|
||||
optparse.make_option("-b", "--backannotated", action="store_true", dest="run_pex",
|
||||
optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex",
|
||||
help="Back annotate simulation"),
|
||||
optparse.make_option("-o", "--output", dest="output_name",
|
||||
help="Base output file name(s) prefix", metavar="FILE"),
|
||||
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 inline LVS/DRC checks", dest="check_lvsdrc"),
|
||||
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",
|
||||
|
|
@ -57,7 +59,7 @@ def parse_args():
|
|||
# This may be overridden when we read a config file though...
|
||||
if OPTS.tech_name == "":
|
||||
OPTS.tech_name = "scmos"
|
||||
# Alias SCMOS to AMI 0.5um
|
||||
# Alias SCMOS to 180nm
|
||||
if OPTS.tech_name == "scmos":
|
||||
OPTS.tech_name = "scn4m_subm"
|
||||
|
||||
|
|
@ -87,6 +89,7 @@ def print_banner():
|
|||
print("|=========" + dev_info.center(60) + "=========|")
|
||||
temp_info = "Temp dir: {}".format(OPTS.openram_temp)
|
||||
print("|=========" + temp_info.center(60) + "=========|")
|
||||
print("|=========" + "See LICENSE for license info".center(60) + "=========|")
|
||||
print("|==============================================================================|")
|
||||
|
||||
|
||||
|
|
@ -100,9 +103,16 @@ def check_versions():
|
|||
minor_required = 5
|
||||
if not (major_python_version == major_required and minor_python_version >= minor_required):
|
||||
debug.error("Python {0}.{1} or greater is required.".format(major_required,minor_required),-1)
|
||||
|
||||
|
||||
# FIXME: Check versions of other tools here??
|
||||
# or, this could be done in each module (e.g. verify, characterizer, etc.)
|
||||
global OPTS
|
||||
|
||||
try:
|
||||
import coverage
|
||||
OPTS.coverage = 1
|
||||
except:
|
||||
OPTS.coverage = 0
|
||||
|
||||
def init_openram(config_file, is_unit_test=True):
|
||||
"""Initialize the technology, paths, simulators, etc."""
|
||||
|
|
@ -189,6 +199,7 @@ def read_config(config_file, is_unit_test=True):
|
|||
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
|
||||
dir_name = os.path.dirname(config_file)
|
||||
file_name = os.path.basename(config_file)
|
||||
|
|
@ -236,7 +247,7 @@ def read_config(config_file, is_unit_test=True):
|
|||
OPTS.num_words,
|
||||
ports,
|
||||
OPTS.tech_name)
|
||||
|
||||
|
||||
|
||||
|
||||
def end_openram():
|
||||
|
|
@ -287,7 +298,8 @@ def setup_paths():
|
|||
|
||||
# Add all of the subdirs to the python path
|
||||
# These subdirs are modules and don't need to be added: characterizer, verify
|
||||
for subdir in ["gdsMill", "tests", "modules", "base", "pgates"]:
|
||||
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
|
||||
for subdir in subdirlist:
|
||||
full_path = "{0}/{1}".format(OPENRAM_HOME,subdir)
|
||||
debug.check(os.path.isdir(full_path),
|
||||
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))
|
||||
|
|
@ -375,13 +387,17 @@ def import_tech():
|
|||
OPTS.temperatures = tech.spice["temperatures"]
|
||||
|
||||
|
||||
def print_time(name, now_time, last_time=None):
|
||||
def print_time(name, now_time, last_time=None, indentation=2):
|
||||
""" Print a statement about the time delta. """
|
||||
if last_time:
|
||||
time = round((now_time-last_time).total_seconds(),1)
|
||||
else:
|
||||
time = now_time
|
||||
print("** {0}: {1} seconds".format(name,time))
|
||||
global OPTS
|
||||
|
||||
# Don't print during testing
|
||||
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')
|
||||
print("{0} {1}: {2}".format("*"*indentation,name,time))
|
||||
|
||||
|
||||
def report_status():
|
||||
|
|
@ -398,12 +414,19 @@ def report_status():
|
|||
debug.error("Tech name must be specified in config file.")
|
||||
|
||||
print("Technology: {0}".format(OPTS.tech_name))
|
||||
print("Total size: {} bits".format(OPTS.word_size*OPTS.num_words*OPTS.num_banks))
|
||||
print("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
|
||||
OPTS.num_words,
|
||||
OPTS.num_banks))
|
||||
print("RW ports: {0}\nR-only ports: {1}\nW-only ports: {2}".format(OPTS.num_rw_ports,
|
||||
OPTS.num_r_ports,
|
||||
OPTS.num_w_ports))
|
||||
if OPTS.netlist_only:
|
||||
print("Netlist only mode (no physical design is being done).")
|
||||
|
||||
if not OPTS.inline_lvsdrc:
|
||||
print("DRC/LVS/PEX is only run on the top-level design.")
|
||||
|
||||
if not OPTS.check_lvsdrc:
|
||||
print("DRC/LVS/PEX checking is disabled.")
|
||||
|
||||
print("DRC/LVS/PEX is completely disabled.")
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,9 +15,11 @@ class bank_select(design.design):
|
|||
banks are created in upper level SRAM module
|
||||
"""
|
||||
|
||||
def __init__(self, name="bank_select"):
|
||||
def __init__(self, name="bank_select", port="rw"):
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.port = port
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
|
@ -25,12 +27,12 @@ class bank_select(design.design):
|
|||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
self.calculate_module_offsets()
|
||||
self.place_modules()
|
||||
self.route_modules()
|
||||
self.place_instances()
|
||||
self.route_instances()
|
||||
|
||||
self.DRC_LVS()
|
||||
|
||||
|
|
@ -38,10 +40,17 @@ class bank_select(design.design):
|
|||
def add_pins(self):
|
||||
|
||||
# Number of control lines in the bus
|
||||
self.num_control_lines = 4
|
||||
if self.port == "rw":
|
||||
self.num_control_lines = 4
|
||||
else:
|
||||
self.num_control_lines = 3
|
||||
# The order of the control signals on the control bus:
|
||||
# FIXME: Update for multiport (these names are not right)
|
||||
self.input_control_signals = ["clk_buf", "clk_buf_bar", "w_en0", "s_en0"]
|
||||
self.input_control_signals = ["clk_buf", "clk_buf_bar"]
|
||||
if (self.port == "rw") or (self.port == "w"):
|
||||
self.input_control_signals.append("w_en")
|
||||
if (self.port == "rw") or (self.port == "r"):
|
||||
self.input_control_signals.append("s_en")
|
||||
# These will be outputs of the gaters if this is multibank
|
||||
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||
|
||||
|
|
@ -53,24 +62,34 @@ class bank_select(design.design):
|
|||
|
||||
def add_modules(self):
|
||||
""" Create modules for later instantiation """
|
||||
from importlib import reload
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
self.bitcell = self.mod_bitcell()
|
||||
|
||||
height = self.bitcell.height + drc("poly_to_active")
|
||||
|
||||
# 1x Inverter
|
||||
self.inv = pinv()
|
||||
self.add_mod(self.inv)
|
||||
self.inv_sel = pinv(height=height)
|
||||
self.add_mod(self.inv_sel)
|
||||
|
||||
# 4x Inverter
|
||||
self.inv4x = pinv(4)
|
||||
self.inv = self.inv4x = pinv(4)
|
||||
self.add_mod(self.inv4x)
|
||||
|
||||
self.nor2 = pnor2()
|
||||
self.nor2 = pnor2(height=height)
|
||||
self.add_mod(self.nor2)
|
||||
|
||||
self.inv4x_nor = pinv(size=4, height=height)
|
||||
self.add_mod(self.inv4x_nor)
|
||||
|
||||
self.nand2 = pnand2()
|
||||
self.add_mod(self.nand2)
|
||||
|
||||
def calculate_module_offsets(self):
|
||||
|
||||
self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc["pwell_to_nwell"]
|
||||
self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc["pwell_to_nwell"]
|
||||
self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_inputs = 0
|
||||
|
|
@ -80,10 +99,10 @@ class bank_select(design.design):
|
|||
self.height = self.yoffset_maxpoint + 2*self.m1_pitch
|
||||
self.width = self.xoffset_inv + self.inv4x.width
|
||||
|
||||
def create_modules(self):
|
||||
def create_instances(self):
|
||||
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
mod=self.inv)
|
||||
mod=self.inv_sel)
|
||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||
|
||||
self.logic_inst = []
|
||||
|
|
@ -107,6 +126,14 @@ class bank_select(design.design):
|
|||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x_nor))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# the rest are AND (nand2+inv) gates
|
||||
else:
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
|
|
@ -117,15 +144,15 @@ class bank_select(design.design):
|
|||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
def place_modules(self):
|
||||
def place_instances(self):
|
||||
|
||||
# bank select inverter
|
||||
self.bank_select_inv_position = vector(self.xoffset_bank_sel_inv, 0)
|
||||
|
|
@ -140,7 +167,11 @@ class bank_select(design.design):
|
|||
|
||||
input_name = self.input_control_signals[i]
|
||||
|
||||
y_offset = self.inv.height * i
|
||||
if i == 0:
|
||||
y_offset = 0
|
||||
else:
|
||||
y_offset = self.inv4x_nor.height + self.inv.height * (i-1)
|
||||
|
||||
if i%2:
|
||||
y_offset += self.inv.height
|
||||
mirror = "MX"
|
||||
|
|
@ -164,7 +195,7 @@ class bank_select(design.design):
|
|||
mirror=mirror)
|
||||
|
||||
|
||||
def route_modules(self):
|
||||
def route_instances(self):
|
||||
|
||||
# bank_sel is vertical wire
|
||||
bank_sel_inv_pin = self.bank_sel_inv.get_pin("A")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from tech import drc, spice
|
|||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
unique_id = 1
|
||||
|
||||
class bitcell_array(design.design):
|
||||
"""
|
||||
|
|
@ -12,8 +12,13 @@ class bitcell_array(design.design):
|
|||
and word line is connected by abutment.
|
||||
Connects the word lines and bit lines.
|
||||
"""
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, cols, rows, name=""):
|
||||
|
||||
def __init__(self, cols, rows, name="bitcell_array"):
|
||||
if name == "":
|
||||
name = "bitcell_array_{0}x{1}_{2}".format(rows,cols,bitcell_array.unique_id)
|
||||
bitcell_array.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
|
||||
|
||||
|
|
@ -34,12 +39,12 @@ class bitcell_array(design.design):
|
|||
""" Create and connect the netlist """
|
||||
self.add_modules()
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
|
||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||
self.height = self.row_size*self.cell.height + drc["well_enclosure_active"] + self.m1_width
|
||||
self.height = self.row_size*self.cell.height + drc("well_enclosure_active") + self.m1_width
|
||||
self.width = self.column_size*self.cell.width + self.m1_width
|
||||
|
||||
xoffset = 0.0
|
||||
|
|
@ -69,10 +74,10 @@ class bitcell_array(design.design):
|
|||
column_list = self.cell.list_all_bitline_names()
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
self.add_pin(cell_column+"[{0}]".format(col))
|
||||
self.add_pin(cell_column+"_{0}".format(col))
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
self.add_pin(cell_row+"[{0}]".format(row))
|
||||
self.add_pin(cell_row+"_{0}".format(row))
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
|
|
@ -85,7 +90,7 @@ class bitcell_array(design.design):
|
|||
self.cell = self.mod_bitcell()
|
||||
self.add_mod(self.cell)
|
||||
|
||||
def create_modules(self):
|
||||
def create_instances(self):
|
||||
""" Create the module instances used in this design """
|
||||
self.cell_inst = {}
|
||||
for col in range(self.column_size):
|
||||
|
|
@ -105,7 +110,7 @@ class bitcell_array(design.design):
|
|||
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),
|
||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=bl_pin.width(),
|
||||
|
|
@ -118,7 +123,7 @@ class bitcell_array(design.design):
|
|||
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),
|
||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||
layer="metal1",
|
||||
offset=wl_pin.ll(),
|
||||
width=self.width,
|
||||
|
|
@ -127,40 +132,14 @@ class bitcell_array(design.design):
|
|||
# increments to the next row height
|
||||
offset.y += self.cell.height
|
||||
|
||||
# For every second row and column, add a via for vdd
|
||||
# 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 vdd_pin in inst.get_pins("vdd"):
|
||||
# Drop to M1 if needed
|
||||
if vdd_pin.layer == "metal1":
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=vdd_pin.center(),
|
||||
rotate=90)
|
||||
# Always drop to M2
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=vdd_pin.center())
|
||||
self.add_layout_pin_rect_center(text="vdd",
|
||||
layer="metal3",
|
||||
offset=vdd_pin.center())
|
||||
|
||||
|
||||
# For every second row and column (+1), add a via for gnd
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row,col]
|
||||
for gnd_pin in inst.get_pins("gnd"):
|
||||
# Drop to M1 if needed
|
||||
if gnd_pin.layer == "metal1":
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=gnd_pin.center(),
|
||||
rotate=90)
|
||||
# Always drop to M2
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=gnd_pin.center())
|
||||
self.add_layout_pin_rect_center(text="gnd",
|
||||
layer="metal3",
|
||||
offset=gnd_pin.center())
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
self.add_power_pin(pin_name, pin.center(), 0, pin.layer)
|
||||
|
||||
|
||||
def analytical_delay(self, slew, load=0):
|
||||
from tech import drc
|
||||
|
|
@ -199,13 +178,21 @@ class bitcell_array(design.design):
|
|||
return total_power
|
||||
|
||||
def gen_wl_wire(self):
|
||||
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
|
||||
if OPTS.netlist_only:
|
||||
width = 0
|
||||
else:
|
||||
width = self.width
|
||||
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_metal1"))
|
||||
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
|
||||
return wl_wire
|
||||
|
||||
def gen_bl_wire(self):
|
||||
if OPTS.netlist_only:
|
||||
height = 0
|
||||
else:
|
||||
height = self.height
|
||||
bl_pos = 0
|
||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
|
||||
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), height, drc("minwidth_metal1"))
|
||||
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
|
||||
return bl_wire
|
||||
|
||||
|
|
@ -217,3 +204,10 @@ class bitcell_array(design.design):
|
|||
def input_load(self):
|
||||
wl_wire = self.gen_wl_wire()
|
||||
return wl_wire.return_input_cap()
|
||||
|
||||
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
|
||||
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||
total_cin = bitcell_wl_cin * self.column_size
|
||||
return total_cin
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -13,8 +13,12 @@ class delay_chain(design.design):
|
|||
Usually, this will be constant, but it could have varied fanout.
|
||||
"""
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, fanout_list, name="delay_chain"):
|
||||
"""init function"""
|
||||
name = name+"_{}".format(delay_chain.unique_id)
|
||||
delay_chain.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
|
||||
# Two fanouts are needed so that we can route the vdd/gnd connections
|
||||
|
|
@ -215,4 +219,23 @@ class delay_chain(design.design):
|
|||
start=mid_point,
|
||||
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.
|
||||
dc_cin = self.inv.get_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.
|
||||
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+=ext_delayed_en_cout
|
||||
stage = self.inv.get_effort_stage(stage_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage)
|
||||
last_stage_is_rise = stage.is_rise
|
||||
|
||||
return stage_effort_list
|
||||
|
|
@ -2,7 +2,7 @@ import globals
|
|||
import design
|
||||
from math import log
|
||||
import design
|
||||
from tech import GDS,layer
|
||||
from tech import GDS,layer,spice,parameter
|
||||
import utils
|
||||
|
||||
class dff(design.design):
|
||||
|
|
@ -12,7 +12,7 @@ class dff(design.design):
|
|||
|
||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
||||
|
||||
def __init__(self, name="dff"):
|
||||
design.design.__init__(self, name)
|
||||
|
|
@ -23,7 +23,6 @@ class dff(design.design):
|
|||
|
||||
def analytical_power(self, proc, vdd, temp, load):
|
||||
"""Returns dynamic and leakage power. Results in nW"""
|
||||
from tech import spice
|
||||
c_eff = self.calculate_effective_capacitance(load)
|
||||
f = spice["default_event_rate"]
|
||||
power_dyn = c_eff*vdd*vdd*f
|
||||
|
|
@ -34,7 +33,7 @@ class dff(design.design):
|
|||
|
||||
def calculate_effective_capacitance(self, load):
|
||||
"""Computes effective capacitance. Results in fF"""
|
||||
from tech import spice, parameter
|
||||
from tech import parameter
|
||||
c_load = load
|
||||
c_para = spice["flop_para_cap"]#ff
|
||||
transition_prob = spice["flop_transition_prob"]
|
||||
|
|
@ -42,7 +41,12 @@ class dff(design.design):
|
|||
|
||||
def analytical_delay(self, slew, load = 0.0):
|
||||
# dont know how to calculate this now, use constant in tech file
|
||||
from tech import spice
|
||||
result = self.return_delay(spice["dff_delay"], spice["dff_slew"])
|
||||
return result
|
||||
|
||||
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.
|
||||
return parameter["dff_clk_cin"]
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class dff_array(design.design):
|
|||
self.dff_insts={}
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
self.connect_inst([self.get_din_name(row,col),
|
||||
|
|
@ -71,7 +71,7 @@ class dff_array(design.design):
|
|||
def place_dff_array(self):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
if (row % 2 == 0):
|
||||
base = vector(col*self.dff.width,row*self.dff.height)
|
||||
mirror = "R0"
|
||||
|
|
@ -83,21 +83,21 @@ class dff_array(design.design):
|
|||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
din_name = "din[{0}]".format(row)
|
||||
din_name = "din_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
din_name = "din[{0}]".format(col)
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din[{0}][{1}]".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
|
||||
return din_name
|
||||
|
||||
def get_dout_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
dout_name = "dout[{0}]".format(row)
|
||||
dout_name = "dout_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
dout_name = "dout[{0}]".format(col)
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
|
||||
return dout_name
|
||||
|
||||
|
|
@ -136,11 +136,12 @@ class dff_array(design.design):
|
|||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
start=vector(0,self.m3_pitch+self.m3_width),
|
||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
# Make a vertical strip for each column
|
||||
|
|
@ -150,9 +151,15 @@ class dff_array(design.design):
|
|||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
|
||||
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
return self.dff.analytical_delay(slew=slew, load=load)
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
dff_clk_cin = self.dff.get_clk_cin()
|
||||
total_cin = dff_clk_cin * self.rows * self.columns
|
||||
return total_cin
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
from tech import drc,parameter
|
||||
from math import log
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
|
@ -12,11 +12,13 @@ class dff_buf(design.design):
|
|||
with two inverters, of variable size, to provide q
|
||||
and qbar. This is to enable driving large fanout loads.
|
||||
"""
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, inv1_size=2, inv2_size=4, name=""):
|
||||
|
||||
if name=="":
|
||||
name = "dff_buf_{0}_{1}".format(inv1_size, inv2_size)
|
||||
name = "dff_buf_{0}".format(dff_buf.unique_id)
|
||||
dff_buf.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
|
||||
|
|
@ -36,13 +38,13 @@ class dff_buf(design.design):
|
|||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
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_modules()
|
||||
self.place_instances()
|
||||
self.route_wires()
|
||||
self.add_layout_pins()
|
||||
self.DRC_LVS()
|
||||
|
|
@ -70,7 +72,7 @@ class dff_buf(design.design):
|
|||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def create_modules(self):
|
||||
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"])
|
||||
|
|
@ -83,7 +85,7 @@ class dff_buf(design.design):
|
|||
mod=self.inv2)
|
||||
self.connect_inst(["Qb", "Q", "vdd", "gnd"])
|
||||
|
||||
def place_modules(self):
|
||||
def place_instances(self):
|
||||
# Add the DFF
|
||||
self.dff_inst.place(vector(0,0))
|
||||
|
||||
|
|
@ -100,8 +102,7 @@ class dff_buf(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",
|
||||
[q_pin.center(), mid1, mid2, a1_pin.center()])
|
||||
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"),
|
||||
|
|
@ -112,8 +113,10 @@ class dff_buf(design.design):
|
|||
# Route inv1 z to inv2 a
|
||||
z1_pin = self.inv1_inst.get_pin("Z")
|
||||
a2_pin = self.inv2_inst.get_pin("A")
|
||||
mid_point = vector(z1_pin.cx(), a2_pin.cy())
|
||||
self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()])
|
||||
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()])
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
|
|
@ -148,18 +151,22 @@ 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)
|
||||
self.add_layout_pin_rect_center(text="Q",
|
||||
layer="metal2",
|
||||
offset=dout_pin.center())
|
||||
offset=q_pos)
|
||||
self.add_path("metal1", [dout_pin.center(), mid_pos, q_pos])
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=dout_pin.center())
|
||||
offset=q_pos)
|
||||
|
||||
dout_pin = self.inv2_inst.get_pin("A")
|
||||
qb_pos = self.mid_qb_pos + vector(0,self.m2_pitch)
|
||||
self.add_layout_pin_rect_center(text="Qb",
|
||||
layer="metal2",
|
||||
offset=dout_pin.center())
|
||||
offset=qb_pos)
|
||||
self.add_path("metal1", [self.mid_qb_pos, qb_pos])
|
||||
self.add_via_center(layers=("metal1","via1","metal2"),
|
||||
offset=dout_pin.center())
|
||||
offset=qb_pos)
|
||||
|
||||
|
||||
|
||||
|
|
@ -170,3 +177,9 @@ class dff_buf(design.design):
|
|||
inv2_delay = self.inv2.analytical_delay(slew=inv1_delay.slew, load=load)
|
||||
return dff_delay + inv1_delay + inv2_delay
|
||||
|
||||
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.
|
||||
return parameter["dff_clk_cin"]
|
||||
|
|
@ -11,13 +11,15 @@ class dff_buf_array(design.design):
|
|||
This is a simple row (or multiple rows) of flops.
|
||||
Unlike the data flops, these are never spaced out.
|
||||
"""
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, rows, columns, inv1_size=2, inv2_size=4, name=""):
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
|
||||
if name=="":
|
||||
name = "dff_buf_array_{0}x{1}".format(rows, columns)
|
||||
name = "dff_buf_array_{0}x{1}_{2}".format(rows, columns, dff_buf_array.unique_id)
|
||||
dff_buf_array.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
self.inv1_size = inv1_size
|
||||
|
|
@ -59,7 +61,7 @@ class dff_buf_array(design.design):
|
|||
self.dff_insts={}
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "Xdff_r{0}_c{1}".format(row,col)
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
self.connect_inst([self.get_din_name(row,col),
|
||||
|
|
@ -84,31 +86,31 @@ class dff_buf_array(design.design):
|
|||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
din_name = "din[{0}]".format(row)
|
||||
din_name = "din_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
din_name = "din[{0}]".format(col)
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din[{0}][{1}]".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
|
||||
return din_name
|
||||
|
||||
def get_dout_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
dout_name = "dout[{0}]".format(row)
|
||||
dout_name = "dout_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
dout_name = "dout[{0}]".format(col)
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
|
||||
return dout_name
|
||||
|
||||
def get_dout_bar_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
dout_bar_name = "dout_bar[{0}]".format(row)
|
||||
dout_bar_name = "dout_bar_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
dout_bar_name = "dout_bar[{0}]".format(col)
|
||||
dout_bar_name = "dout_bar_{0}".format(col)
|
||||
else:
|
||||
dout_bar_name = "dout_bar[{0}][{1}]".format(row,col)
|
||||
dout_bar_name = "dout_bar_{0}_{1}".format(row,col)
|
||||
|
||||
return dout_bar_name
|
||||
|
||||
|
|
@ -153,6 +155,7 @@ class dff_buf_array(design.design):
|
|||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
if self.columns==1:
|
||||
self.add_layout_pin(text="clk",
|
||||
|
|
@ -163,8 +166,8 @@ class dff_buf_array(design.design):
|
|||
else:
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
start=vector(0,self.m3_pitch+self.m3_width),
|
||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
|
||||
|
|
@ -175,9 +178,15 @@ class dff_buf_array(design.design):
|
|||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
|
||||
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
return self.dff.analytical_delay(slew=slew, load=load)
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
dff_clk_cin = self.dff.get_clk_cin()
|
||||
total_cin = dff_clk_cin * self.rows * self.columns
|
||||
return total_cin
|
||||
|
|
@ -11,11 +11,13 @@ class dff_inv(design.design):
|
|||
This is a simple DFF with an inverted output. Some DFFs
|
||||
do not have Qbar, so this will create it.
|
||||
"""
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, inv_size=2, name=""):
|
||||
|
||||
if name=="":
|
||||
name = "dff_inv_{0}".format(inv_size)
|
||||
name = "dff_inv_{0}".format(dff_inv.unique_id)
|
||||
dff_inv.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
self.inv_size = inv_size
|
||||
|
|
@ -148,3 +150,6 @@ class dff_inv(design.design):
|
|||
inv1_delay = self.inv1.analytical_delay(slew=dff_delay.slew, load=load)
|
||||
return dff_delay + inv1_delay
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff"""
|
||||
return self.dff.get_clk_cin()
|
||||
|
|
@ -11,13 +11,15 @@ class dff_inv_array(design.design):
|
|||
This is a simple row (or multiple rows) of flops.
|
||||
Unlike the data flops, these are never spaced out.
|
||||
"""
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, rows, columns, inv_size=2, name=""):
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
|
||||
if name=="":
|
||||
name = "dff_inv_array_{0}x{1}".format(rows, columns)
|
||||
name = "dff_inv_array_{0}x{1}_{2}".format(rows, columns, dff_inv_array.unique_id)
|
||||
dff_inv_array.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
self.inv_size = inv_size
|
||||
|
|
@ -84,31 +86,31 @@ class dff_inv_array(design.design):
|
|||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
din_name = "din[{0}]".format(row)
|
||||
din_name = "din_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
din_name = "din[{0}]".format(col)
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din[{0}][{1}]".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
|
||||
return din_name
|
||||
|
||||
def get_dout_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
dout_name = "dout[{0}]".format(row)
|
||||
dout_name = "dout_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
dout_name = "dout[{0}]".format(col)
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout[{0}][{1}]".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
|
||||
return dout_name
|
||||
|
||||
def get_dout_bar_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
dout_bar_name = "dout_bar[{0}]".format(row)
|
||||
dout_bar_name = "dout_bar_{0}".format(row)
|
||||
elif self.rows == 1:
|
||||
dout_bar_name = "dout_bar[{0}]".format(col)
|
||||
dout_bar_name = "dout_bar_{0}".format(col)
|
||||
else:
|
||||
dout_bar_name = "dout_bar[{0}][{1}]".format(row,col)
|
||||
dout_bar_name = "dout_bar_{0}_{1}".format(row,col)
|
||||
|
||||
return dout_bar_name
|
||||
|
||||
|
|
@ -153,6 +155,7 @@ class dff_inv_array(design.design):
|
|||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin("clk")
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
|
||||
if self.columns==1:
|
||||
self.add_layout_pin(text="clk",
|
||||
|
|
@ -163,8 +166,8 @@ class dff_inv_array(design.design):
|
|||
else:
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="metal3",
|
||||
start=vector(0,self.m3_pitch+self.m3_width),
|
||||
end=vector(self.width,self.m3_pitch+self.m3_width))
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin("clk")
|
||||
# Make a vertical strip for each column
|
||||
|
|
@ -174,10 +177,16 @@ class dff_inv_array(design.design):
|
|||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=("metal2","via2","metal3"),
|
||||
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
|
||||
|
||||
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
return self.dff.analytical_delay(slew=slew, load=load)
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
dff_clk_cin = self.dff.get_clk_cin()
|
||||
total_cin = dff_clk_cin * self.rows * self.columns
|
||||
return total_cin
|
||||
|
|
@ -17,22 +17,19 @@ class hierarchical_decoder(design.design):
|
|||
"""
|
||||
Dynamically generated hierarchical decoder.
|
||||
"""
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, rows, height=None):
|
||||
design.design.__init__(self, "hierarchical_decoder_{0}rows_{1}".format(rows,hierarchical_decoder.unique_id))
|
||||
hierarchical_decoder.unique_id += 1
|
||||
|
||||
def __init__(self, rows):
|
||||
design.design.__init__(self, "hierarchical_decoder_{0}rows".format(rows))
|
||||
|
||||
from importlib import reload
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
b = self.mod_bitcell()
|
||||
self.bitcell_height = b.height
|
||||
|
||||
self.NAND_FORMAT = "DEC_NAND[{0}]"
|
||||
self.INV_FORMAT = "DEC_INV_[{0}]"
|
||||
self.NAND_FORMAT = "DEC_NAND_{0}"
|
||||
self.INV_FORMAT = "DEC_INV_{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)
|
||||
|
|
@ -60,21 +57,21 @@ class hierarchical_decoder(design.design):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_modules(self):
|
||||
self.inv = pinv()
|
||||
self.inv = pinv(height=self.cell_height)
|
||||
self.add_mod(self.inv)
|
||||
self.nand2 = pnand2()
|
||||
self.nand2 = pnand2(height=self.cell_height)
|
||||
self.add_mod(self.nand2)
|
||||
self.nand3 = pnand3()
|
||||
self.nand3 = pnand3(height=self.cell_height)
|
||||
self.add_mod(self.nand3)
|
||||
|
||||
self.add_decoders()
|
||||
|
||||
def add_decoders(self):
|
||||
""" Create the decoders based on the number of pre-decodes """
|
||||
self.pre2_4 = pre2x4()
|
||||
self.pre2_4 = pre2x4(height=self.cell_height)
|
||||
self.add_mod(self.pre2_4)
|
||||
|
||||
self.pre3_8 = pre3x8()
|
||||
self.pre3_8 = pre3x8(height=self.cell_height)
|
||||
self.add_mod(self.pre3_8)
|
||||
|
||||
def determine_predecodes(self,num_inputs):
|
||||
|
|
@ -168,7 +165,7 @@ class hierarchical_decoder(design.design):
|
|||
min_x = min(min_x, -self.pre3_8.width)
|
||||
input_offset=vector(min_x - self.input_routing_width,0)
|
||||
|
||||
input_bus_names = ["addr[{0}]".format(i) for i in range(self.num_inputs)]
|
||||
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,
|
||||
|
|
@ -184,9 +181,9 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(2):
|
||||
index = pre_num * 2 + i
|
||||
|
||||
input_pos = self.input_rails["addr[{}]".format(index)]
|
||||
input_pos = self.input_rails["addr_{}".format(index)]
|
||||
|
||||
in_name = "in[{}]".format(i)
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||
|
||||
# To prevent conflicts, we will offset each input connect so
|
||||
|
|
@ -201,9 +198,9 @@ class hierarchical_decoder(design.design):
|
|||
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_rails["addr_{}".format(index)]
|
||||
|
||||
in_name = "in[{}]".format(i)
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||
|
||||
# To prevent conflicts, we will offset each input connect so
|
||||
|
|
@ -230,10 +227,10 @@ class hierarchical_decoder(design.design):
|
|||
""" Add the module pins """
|
||||
|
||||
for i in range(self.num_inputs):
|
||||
self.add_pin("addr[{0}]".format(i))
|
||||
self.add_pin("addr_{0}".format(i))
|
||||
|
||||
for j in range(self.rows):
|
||||
self.add_pin("decode[{0}]".format(j))
|
||||
self.add_pin("decode_{0}".format(j))
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
|
|
@ -258,12 +255,12 @@ class hierarchical_decoder(design.design):
|
|||
|
||||
pins = []
|
||||
for input_index in range(2):
|
||||
pins.append("addr[{0}]".format(input_index + index_off1))
|
||||
pins.append("addr_{0}".format(input_index + index_off1))
|
||||
for output_index in range(4):
|
||||
pins.append("out[{0}]".format(output_index + index_off2))
|
||||
pins.append("out_{0}".format(output_index + index_off2))
|
||||
pins.extend(["vdd", "gnd"])
|
||||
|
||||
self.pre2x4_inst.append(self.add_inst(name="pre[{0}]".format(num),
|
||||
self.pre2x4_inst.append(self.add_inst(name="pre_{0}".format(num),
|
||||
mod=self.pre2_4))
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
|
@ -277,12 +274,12 @@ class hierarchical_decoder(design.design):
|
|||
|
||||
pins = []
|
||||
for input_index in range(3):
|
||||
pins.append("addr[{0}]".format(input_index + in_index_offset))
|
||||
pins.append("addr_{0}".format(input_index + in_index_offset))
|
||||
for output_index in range(8):
|
||||
pins.append("out[{0}]".format(output_index + out_index_offset))
|
||||
pins.append("out_{0}".format(output_index + out_index_offset))
|
||||
pins.extend(["vdd", "gnd"])
|
||||
|
||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8[{0}]".format(num),
|
||||
self.pre3x8_inst.append(self.add_inst(name="pre3x8_{0}".format(num),
|
||||
mod=self.pre3_8))
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
|
@ -336,13 +333,13 @@ class hierarchical_decoder(design.design):
|
|||
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[1])*i + j
|
||||
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),
|
||||
pins =["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"Z_{0}".format(row),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
|
@ -352,17 +349,17 @@ class hierarchical_decoder(design.design):
|
|||
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[1])*len(self.predec_groups[2]) * i \
|
||||
+ len(self.predec_groups[2])*j + k
|
||||
row = (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))
|
||||
|
||||
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),
|
||||
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)
|
||||
|
||||
|
|
@ -377,8 +374,8 @@ class hierarchical_decoder(design.design):
|
|||
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),
|
||||
self.connect_inst(args=["Z_{0}".format(row),
|
||||
"decode_{0}".format(row),
|
||||
"vdd", "gnd"])
|
||||
|
||||
|
||||
|
|
@ -466,7 +463,7 @@ class hierarchical_decoder(design.design):
|
|||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||
|
||||
z_pin = self.inv_inst[row].get_pin("Z")
|
||||
self.add_layout_pin(text="decode[{0}]".format(row),
|
||||
self.add_layout_pin(text="decode_{0}".format(row),
|
||||
layer="metal1",
|
||||
offset=z_pin.ll(),
|
||||
width=z_pin.width(),
|
||||
|
|
@ -480,7 +477,7 @@ class hierarchical_decoder(design.design):
|
|||
# This is not needed for inputs <4 since they have no pre/decode stages.
|
||||
if (self.num_inputs >= 4):
|
||||
input_offset = vector(0.5*self.m2_width,0)
|
||||
input_bus_names = ["predecode[{0}]".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
||||
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
||||
self.predecode_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
|
|
@ -497,8 +494,8 @@ class hierarchical_decoder(design.design):
|
|||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre2x4):
|
||||
for i in range(4):
|
||||
predecode_name = "predecode[{}]".format(pre_num * 4 + i)
|
||||
out_name = "out[{}]".format(i)
|
||||
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
|
||||
|
|
@ -506,8 +503,8 @@ class hierarchical_decoder(design.design):
|
|||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre3x8):
|
||||
for i in range(8):
|
||||
predecode_name = "predecode[{}]".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||
out_name = "out[{}]".format(i)
|
||||
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
|
||||
|
|
@ -523,55 +520,52 @@ class hierarchical_decoder(design.design):
|
|||
"""
|
||||
row_index = 0
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
for index_A in self.predec_groups[0]:
|
||||
for index_B in self.predec_groups[1]:
|
||||
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)
|
||||
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)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||
row_index = row_index + 1
|
||||
|
||||
elif (self.num_inputs > 5):
|
||||
for index_A in self.predec_groups[0]:
|
||||
for index_C in self.predec_groups[2]:
|
||||
for index_B in self.predec_groups[1]:
|
||||
for index_C in self.predec_groups[2]:
|
||||
for index_A in self.predec_groups[0]:
|
||||
# FIXME: convert to connect_bus?
|
||||
predecode_name = "predecode[{}]".format(index_A)
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("A"))
|
||||
predecode_name = "predecode[{}]".format(index_B)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("B"))
|
||||
predecode_name = "predecode[{}]".format(index_C)
|
||||
predecode_name = "predecode_{}".format(index_C)
|
||||
self.route_predecode_rail(predecode_name, self.nand_inst[row_index].get_pin("C"))
|
||||
row_index = row_index + 1
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
a_xoffset = self.inv_inst[0].lx()
|
||||
b_xoffset = self.inv_inst[0].rx()
|
||||
|
||||
# The vias will be placed in the center and right of the cells, respectively.
|
||||
xoffset = self.nand_inst[0].cx()
|
||||
for num in range(0,self.rows):
|
||||
# this will result in duplicate polygons for rails, but who cares
|
||||
|
||||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
supply_pin = self.inv_inst[num].get_pin(n)
|
||||
|
||||
# Add pins in two locations
|
||||
for xoffset in [a_xoffset, b_xoffset]:
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=pin_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=pin_pos,
|
||||
rotate=90)
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="metal3",
|
||||
offset=pin_pos)
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# The nand and inv are the same height rows...
|
||||
supply_pin = self.nand_inst[num].get_pin(pin_name)
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin_pos)
|
||||
|
||||
# Make a redundant rail too
|
||||
for num in range(0,self.rows,2):
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
start = self.nand_inst[num].get_pin(pin_name).lc()
|
||||
end = self.inv_inst[num].get_pin(pin_name).rc()
|
||||
mid = (start+end).scale(0.5,0.5)
|
||||
self.add_rect_center(layer="metal1",
|
||||
offset=mid,
|
||||
width=end.x-start.x)
|
||||
|
||||
|
||||
# Copy the pins from the predecoders
|
||||
for pre in self.pre2x4_inst + self.pre3x8_inst:
|
||||
self.copy_layout_pin(pre, "vdd")
|
||||
|
|
|
|||
|
|
@ -9,32 +9,31 @@ from globals import OPTS
|
|||
from pnand2 import pnand2
|
||||
from pnand3 import pnand3
|
||||
|
||||
|
||||
class hierarchical_predecode(design.design):
|
||||
"""
|
||||
Pre 2x4 and 3x8 decoder shared code.
|
||||
"""
|
||||
def __init__(self, input_number):
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, input_number, height=None):
|
||||
self.number_of_inputs = input_number
|
||||
self.cell_height = height
|
||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||
design.design.__init__(self, name="pre{0}x{1}".format(self.number_of_inputs,self.number_of_outputs))
|
||||
|
||||
from importlib import reload
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
design.design.__init__(self, name="pre{0}x{1}_{2}".format(self.number_of_inputs,self.number_of_outputs,hierarchical_predecode.unique_id))
|
||||
hierarchical_predecode.unique_id += 1
|
||||
|
||||
def add_pins(self):
|
||||
for k in range(self.number_of_inputs):
|
||||
self.add_pin("in[{0}]".format(k))
|
||||
self.add_pin("in_{0}".format(k))
|
||||
for i in range(self.number_of_outputs):
|
||||
self.add_pin("out[{0}]".format(i))
|
||||
self.add_pin("out_{0}".format(i))
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def create_modules(self):
|
||||
""" Create the INV and NAND gate """
|
||||
def add_modules(self):
|
||||
""" Add the INV and NAND gate modules """
|
||||
|
||||
self.inv = pinv()
|
||||
self.inv = pinv(height=self.cell_height)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
self.add_nand(self.number_of_inputs)
|
||||
|
|
@ -43,9 +42,9 @@ class hierarchical_predecode(design.design):
|
|||
def add_nand(self,inputs):
|
||||
""" Create the NAND for the predecode input stage """
|
||||
if inputs==2:
|
||||
self.nand = pnand2()
|
||||
self.nand = pnand2(height=self.cell_height)
|
||||
elif inputs==3:
|
||||
self.nand = pnand3()
|
||||
self.nand = pnand3(height=self.cell_height)
|
||||
else:
|
||||
debug.error("Invalid number of predecode inputs: {}".format(inputs),-1)
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ class hierarchical_predecode(design.design):
|
|||
|
||||
def route_rails(self):
|
||||
""" Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """
|
||||
input_names = ["in[{}]".format(x) for x in range(self.number_of_inputs)]
|
||||
input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
offset = vector(0.5*self.m2_width,2*self.m1_width)
|
||||
self.input_rails = self.create_vertical_pin_bus(layer="metal2",
|
||||
pitch=self.m2_pitch,
|
||||
|
|
@ -75,8 +74,8 @@ class hierarchical_predecode(design.design):
|
|||
names=input_names,
|
||||
length=self.height - 2*self.m1_width)
|
||||
|
||||
invert_names = ["Abar[{}]".format(x) for x in range(self.number_of_inputs)]
|
||||
non_invert_names = ["A[{}]".format(x) for x in range(self.number_of_inputs)]
|
||||
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
|
||||
decode_names = invert_names + non_invert_names
|
||||
offset = vector(self.x_off_inv_1 + self.inv.width + 2*self.m2_pitch, 2*self.m1_width)
|
||||
self.decode_rails = self.create_vertical_bus(layer="metal2",
|
||||
|
|
@ -90,11 +89,11 @@ class hierarchical_predecode(design.design):
|
|||
""" Create the input inverters to invert input signals for the decode stage. """
|
||||
self.in_inst = []
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
name = "Xpre_inv[{0}]".format(inv_num)
|
||||
name = "pre_inv_{0}".format(inv_num)
|
||||
self.in_inst.append(self.add_inst(name=name,
|
||||
mod=self.inv))
|
||||
self.connect_inst(["in[{0}]".format(inv_num),
|
||||
"inbar[{0}]".format(inv_num),
|
||||
self.connect_inst(["in_{0}".format(inv_num),
|
||||
"inbar_{0}".format(inv_num),
|
||||
"vdd", "gnd"])
|
||||
|
||||
def place_input_inverters(self):
|
||||
|
|
@ -114,11 +113,11 @@ class hierarchical_predecode(design.design):
|
|||
""" Create inverters for the inverted output decode signals. """
|
||||
self.inv_inst = []
|
||||
for inv_num in range(self.number_of_outputs):
|
||||
name = "Xpre_nand_inv[{}]".format(inv_num)
|
||||
name = "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),
|
||||
self.connect_inst(["Z_{}".format(inv_num),
|
||||
"out_{}".format(inv_num),
|
||||
"vdd", "gnd"])
|
||||
|
||||
|
||||
|
|
@ -140,7 +139,7 @@ class hierarchical_predecode(design.design):
|
|||
self.nand_inst = []
|
||||
for nand_input in range(self.number_of_outputs):
|
||||
inout = str(self.number_of_inputs)+"x"+str(self.number_of_outputs)
|
||||
name = "Xpre{0}_nand[{1}]".format(inout,nand_input)
|
||||
name = "Xpre{0}_nand_{1}".format(inout,nand_input)
|
||||
self.nand_inst.append(self.add_inst(name=name,
|
||||
mod=self.nand))
|
||||
self.connect_inst(connections[nand_input])
|
||||
|
|
@ -175,8 +174,8 @@ class hierarchical_predecode(design.design):
|
|||
# typically where the p/n devices are and there are no
|
||||
# pins in the nand gates.
|
||||
y_offset = (num+self.number_of_inputs) * self.inv.height + contact.m1m2.width + self.m1_space
|
||||
in_pin = "in[{}]".format(num)
|
||||
a_pin = "A[{}]".format(num)
|
||||
in_pin = "in_{}".format(num)
|
||||
a_pin = "A_{}".format(num)
|
||||
in_pos = vector(self.input_rails[in_pin].x,y_offset)
|
||||
a_pos = vector(self.decode_rails[a_pin].x,y_offset)
|
||||
self.add_path("metal1",[in_pos, a_pos])
|
||||
|
|
@ -202,7 +201,7 @@ class hierarchical_predecode(design.design):
|
|||
self.add_path("metal1", [zr_pos, mid1_pos, mid2_pos, al_pos])
|
||||
|
||||
z_pin = self.inv_inst[num].get_pin("Z")
|
||||
self.add_layout_pin(text="out[{}]".format(num),
|
||||
self.add_layout_pin(text="out_{}".format(num),
|
||||
layer="metal1",
|
||||
offset=z_pin.ll(),
|
||||
height=z_pin.height(),
|
||||
|
|
@ -214,8 +213,8 @@ class hierarchical_predecode(design.design):
|
|||
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
|
||||
"""
|
||||
for inv_num in range(self.number_of_inputs):
|
||||
out_pin = "Abar[{}]".format(inv_num)
|
||||
in_pin = "in[{}]".format(inv_num)
|
||||
out_pin = "Abar_{}".format(inv_num)
|
||||
in_pin = "in_{}".format(inv_num)
|
||||
|
||||
#add output so that it is just below the vdd or gnd rail
|
||||
# since this is where the p/n devices are and there are no
|
||||
|
|
@ -267,7 +266,7 @@ class hierarchical_predecode(design.design):
|
|||
|
||||
# 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()
|
||||
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
|
||||
|
||||
|
|
@ -282,15 +281,7 @@ class hierarchical_predecode(design.design):
|
|||
# Add pins in two locations
|
||||
for xoffset in [in_xoffset, out_xoffset]:
|
||||
pin_pos = vector(xoffset, nand_pin.cy())
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=pin_pos,
|
||||
rotate=90)
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=pin_pos,
|
||||
rotate=90)
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="metal3",
|
||||
offset=pin_pos)
|
||||
self.add_power_pin(n, pin_pos)
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||
"""
|
||||
Pre 2x4 decoder used in hierarchical_decoder.
|
||||
"""
|
||||
def __init__(self):
|
||||
hierarchical_predecode.__init__(self, 2)
|
||||
def __init__(self, height=None):
|
||||
hierarchical_predecode.__init__(self, 2, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -18,13 +18,13 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
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"]]
|
||||
connections =[["inbar_0", "inbar_1", "Z_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "Z_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "Z_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "Z_3", "vdd", "gnd"]]
|
||||
self.create_nand_array(connections)
|
||||
|
||||
def create_layout(self):
|
||||
|
|
@ -44,10 +44,10 @@ class hierarchical_predecode2x4(hierarchical_predecode):
|
|||
|
||||
def get_nand_input_line_combination(self):
|
||||
""" These are the decoder connections of the NAND gates to the A,B pins """
|
||||
combination = [["Abar[0]", "Abar[1]"],
|
||||
["A[0]", "Abar[1]"],
|
||||
["Abar[0]", "A[1]"],
|
||||
["A[0]", "A[1]"]]
|
||||
combination = [["Abar_0", "Abar_1"],
|
||||
["A_0", "Abar_1"],
|
||||
["Abar_0", "A_1"],
|
||||
["A_0", "A_1"]]
|
||||
return combination
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||
"""
|
||||
Pre 3x8 decoder used in hierarchical_decoder.
|
||||
"""
|
||||
def __init__(self):
|
||||
hierarchical_predecode.__init__(self, 3)
|
||||
def __init__(self, height=None):
|
||||
hierarchical_predecode.__init__(self, 3, height)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -18,17 +18,17 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
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"]]
|
||||
connections=[["inbar_0", "inbar_1", "inbar_2", "Z_0", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "inbar_2", "Z_1", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "inbar_2", "Z_2", "vdd", "gnd"],
|
||||
["in_0", "in_1", "inbar_2", "Z_3", "vdd", "gnd"],
|
||||
["inbar_0", "inbar_1", "in_2", "Z_4", "vdd", "gnd"],
|
||||
["in_0", "inbar_1", "in_2", "Z_5", "vdd", "gnd"],
|
||||
["inbar_0", "in_1", "in_2", "Z_6", "vdd", "gnd"],
|
||||
["in_0", "in_1", "in_2", "Z_7", "vdd", "gnd"]]
|
||||
self.create_nand_array(connections)
|
||||
|
||||
def create_layout(self):
|
||||
|
|
@ -49,14 +49,14 @@ class hierarchical_predecode3x8(hierarchical_predecode):
|
|||
|
||||
def get_nand_input_line_combination(self):
|
||||
""" These are the decoder connections of the NAND gates to the A,B,C pins """
|
||||
combination = [["Abar[0]", "Abar[1]", "Abar[2]"],
|
||||
["A[0]", "Abar[1]", "Abar[2]"],
|
||||
["Abar[0]", "A[1]", "Abar[2]"],
|
||||
["A[0]", "A[1]", "Abar[2]"],
|
||||
["Abar[0]", "Abar[1]", "A[2]"],
|
||||
["A[0]", "Abar[1]", "A[2]"],
|
||||
["Abar[0]", "A[1]", "A[2]"],
|
||||
["A[0]", "A[1]", "A[2]"]]
|
||||
combination = [["Abar_0", "Abar_1", "Abar_2"],
|
||||
["A_0", "Abar_1", "Abar_2"],
|
||||
["Abar_0", "A_1", "Abar_2"],
|
||||
["A_0", "A_1", "Abar_2"],
|
||||
["Abar_0", "Abar_1", "A_2"],
|
||||
["A_0", "Abar_1", "A_2"],
|
||||
["Abar_0", "A_1", "A_2"],
|
||||
["A_0", "A_1", "A_2"]]
|
||||
return combination
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ class multibank(design.design):
|
|||
|
||||
self.compute_sizes()
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
self.add_modules()
|
||||
self.create_instances()
|
||||
self.setup_layout_constraints()
|
||||
|
||||
# FIXME: Move this to the add modules function
|
||||
|
|
@ -75,11 +75,11 @@ class multibank(design.design):
|
|||
def add_pins(self):
|
||||
""" Adding pins for Bank module"""
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("DOUT[{0}]".format(i),"OUT")
|
||||
self.add_pin("DOUT_{0}".format(i),"OUT")
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("BANK_DIN[{0}]".format(i),"IN")
|
||||
self.add_pin("BANK_DIN_{0}".format(i),"IN")
|
||||
for i in range(self.addr_size):
|
||||
self.add_pin("A[{0}]".format(i),"INPUT")
|
||||
self.add_pin("A_{0}".format(i),"INPUT")
|
||||
|
||||
# For more than one bank, we have a bank select and name
|
||||
# the signals gated_*.
|
||||
|
|
@ -109,9 +109,9 @@ class multibank(design.design):
|
|||
if self.num_banks > 1:
|
||||
self.route_bank_select()
|
||||
|
||||
self.route_vdd_gnd()
|
||||
self.route_supplies()
|
||||
|
||||
def add_modules(self):
|
||||
def create_instances(self):
|
||||
""" Add modules. The order should not matter! """
|
||||
|
||||
# Above the bitcell array
|
||||
|
|
@ -170,13 +170,13 @@ 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_enclosure_active"),
|
||||
2*self.m2_pitch)
|
||||
|
||||
|
||||
|
||||
def create_modules(self):
|
||||
""" Create all the modules using the class loader """
|
||||
def add_modules(self):
|
||||
""" Add all the modules using the class loader """
|
||||
self.tri = self.mod_tri_gate()
|
||||
self.bitcell = self.mod_bitcell()
|
||||
|
||||
|
|
@ -227,10 +227,10 @@ class multibank(design.design):
|
|||
offset=vector(0,0))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.append("bl_{0}".format(i))
|
||||
temp.append("br_{0}".format(i))
|
||||
for j in range(self.num_rows):
|
||||
temp.append("wl[{0}]".format(j))
|
||||
temp.append("wl_{0}".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -246,8 +246,8 @@ class multibank(design.design):
|
|||
offset=vector(0,y_offset))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.append("bl_{0}".format(i))
|
||||
temp.append("br_{0}".format(i))
|
||||
temp.extend([self.prefix+"clk_buf_bar", "vdd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -265,13 +265,13 @@ class multibank(design.design):
|
|||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
temp = []
|
||||
for i in range(self.num_cols):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.append("bl_{0}".format(i))
|
||||
temp.append("br_{0}".format(i))
|
||||
for k in range(self.words_per_row):
|
||||
temp.append("sel[{0}]".format(k))
|
||||
temp.append("sel_{0}".format(k))
|
||||
for j in range(self.word_size):
|
||||
temp.append("bl_out[{0}]".format(j))
|
||||
temp.append("br_out[{0}]".format(j))
|
||||
temp.append("bl_out_{0}".format(j))
|
||||
temp.append("br_out_{0}".format(j))
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -284,13 +284,13 @@ class multibank(design.design):
|
|||
offset=vector(0,y_offset).scale(-1,-1))
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("sa_out[{0}]".format(i))
|
||||
temp.append("sa_out_{0}".format(i))
|
||||
if self.words_per_row == 1:
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.append("bl_{0}".format(i))
|
||||
temp.append("br_{0}".format(i))
|
||||
else:
|
||||
temp.append("bl_out[{0}]".format(i))
|
||||
temp.append("br_out[{0}]".format(i))
|
||||
temp.append("bl_out_{0}".format(i))
|
||||
temp.append("br_out_{0}".format(i))
|
||||
|
||||
temp.extend([self.prefix+"s_en", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
|
@ -306,14 +306,14 @@ class multibank(design.design):
|
|||
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("BANK_DIN[{0}]".format(i))
|
||||
temp.append("BANK_DIN_{0}".format(i))
|
||||
for i in range(self.word_size):
|
||||
if (self.words_per_row == 1):
|
||||
temp.append("bl[{0}]".format(i))
|
||||
temp.append("br[{0}]".format(i))
|
||||
temp.append("bl_{0}".format(i))
|
||||
temp.append("br_{0}".format(i))
|
||||
else:
|
||||
temp.append("bl_out[{0}]".format(i))
|
||||
temp.append("br_out[{0}]".format(i))
|
||||
temp.append("bl_out_{0}".format(i))
|
||||
temp.append("br_out_{0}".format(i))
|
||||
temp.extend([self.prefix+"w_en", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -327,9 +327,9 @@ class multibank(design.design):
|
|||
|
||||
temp = []
|
||||
for i in range(self.word_size):
|
||||
temp.append("sa_out[{0}]".format(i))
|
||||
temp.append("sa_out_{0}".format(i))
|
||||
for i in range(self.word_size):
|
||||
temp.append("DOUT[{0}]".format(i))
|
||||
temp.append("DOUT_{0}".format(i))
|
||||
temp.extend([self.prefix+"tri_en", self.prefix+"tri_en_bar", "vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -350,9 +350,9 @@ class multibank(design.design):
|
|||
|
||||
temp = []
|
||||
for i in range(self.row_addr_size):
|
||||
temp.append("A[{0}]".format(i+self.col_addr_size))
|
||||
temp.append("A_{0}".format(i+self.col_addr_size))
|
||||
for j in range(self.num_rows):
|
||||
temp.append("dec_out[{0}]".format(j))
|
||||
temp.append("dec_out_{0}".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -367,9 +367,9 @@ class multibank(design.design):
|
|||
|
||||
temp = []
|
||||
for i in range(self.num_rows):
|
||||
temp.append("dec_out[{0}]".format(i))
|
||||
temp.append("dec_out_{0}".format(i))
|
||||
for i in range(self.num_rows):
|
||||
temp.append("wl[{0}]".format(i))
|
||||
temp.append("wl_{0}".format(i))
|
||||
temp.append(self.prefix+"clk_buf")
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
|
|
@ -382,16 +382,16 @@ class multibank(design.design):
|
|||
"""
|
||||
# Place the col decoder right aligned with row decoder
|
||||
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
|
||||
y_off = -(self.col_decoder.height + 2*drc["well_to_well"])
|
||||
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
|
||||
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
|
||||
mod=self.col_decoder,
|
||||
offset=vector(x_off,y_off))
|
||||
|
||||
temp = []
|
||||
for i in range(self.col_addr_size):
|
||||
temp.append("A[{0}]".format(i))
|
||||
temp.append("A_{0}".format(i))
|
||||
for j in range(self.num_col_addr_lines):
|
||||
temp.append("sel[{0}]".format(j))
|
||||
temp.append("sel_{0}".format(j))
|
||||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
|
|
@ -427,7 +427,7 @@ class multibank(design.design):
|
|||
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
|
||||
else:
|
||||
y_off = self.row_decoder_inst.by()
|
||||
y_off -= (self.bank_select.height + drc["well_to_well"])
|
||||
y_off -= (self.bank_select.height + drc("well_to_well"))
|
||||
self.bank_select_pos = vector(x_off,y_off)
|
||||
self.bank_select_inst = self.add_inst(name="bank_select",
|
||||
mod=self.bank_select,
|
||||
|
|
@ -440,33 +440,11 @@ class multibank(design.design):
|
|||
temp.extend(["vdd", "gnd"])
|
||||
self.connect_inst(temp)
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
def route_supplies(self):
|
||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
|
||||
# These are the instances that every bank has
|
||||
top_instances = [self.bitcell_array_inst,
|
||||
self.precharge_array_inst,
|
||||
self.sense_amp_array_inst,
|
||||
self.write_driver_array_inst,
|
||||
# self.tri_gate_array_inst,
|
||||
self.row_decoder_inst,
|
||||
self.wordline_driver_inst]
|
||||
# Add these if we use the part...
|
||||
if self.col_addr_size > 0:
|
||||
top_instances.append(self.col_decoder_inst)
|
||||
top_instances.append(self.col_mux_array_inst)
|
||||
|
||||
if self.num_banks > 1:
|
||||
top_instances.append(self.bank_select_inst)
|
||||
|
||||
|
||||
for inst in top_instances:
|
||||
# Column mux has no vdd
|
||||
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
# Precharge has no gnd
|
||||
if inst != self.precharge_array_inst:
|
||||
self.copy_layout_pin(inst, "gnd")
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst,"vdd")
|
||||
self.copy_power_pins(inst,"gnd")
|
||||
|
||||
def route_bank_select(self):
|
||||
""" Route the bank select logic. """
|
||||
|
|
@ -550,10 +528,10 @@ class multibank(design.design):
|
|||
""" Routing of BL and BR between pre-charge and bitcell array """
|
||||
|
||||
for i in range(self.num_cols):
|
||||
precharge_bl = self.precharge_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
precharge_br = self.precharge_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
precharge_bl = self.precharge_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||
precharge_br = self.precharge_array_inst.get_pin("br_{}".format(i)).bc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).uc()
|
||||
|
||||
yoffset = 0.5*(precharge_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[precharge_bl, vector(precharge_bl.x,yoffset),
|
||||
|
|
@ -570,10 +548,10 @@ class multibank(design.design):
|
|||
return
|
||||
|
||||
for i in range(self.num_cols):
|
||||
col_mux_bl = self.col_mux_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
col_mux_br = self.col_mux_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
col_mux_bl = self.col_mux_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||
col_mux_br = self.col_mux_array_inst.get_pin("br_{}".format(i)).uc()
|
||||
bitcell_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||
bitcell_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||
|
||||
yoffset = 0.5*(col_mux_bl.y+bitcell_bl.y)
|
||||
self.add_path("metal2",[col_mux_bl, vector(col_mux_bl.x,yoffset),
|
||||
|
|
@ -585,17 +563,17 @@ class multibank(design.design):
|
|||
""" Routing of BL and BR between sense_amp and column mux or bitcell array """
|
||||
|
||||
for i in range(self.word_size):
|
||||
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl[{}]".format(i)).uc()
|
||||
sense_amp_br = self.sense_amp_array_inst.get_pin("br[{}]".format(i)).uc()
|
||||
sense_amp_bl = self.sense_amp_array_inst.get_pin("bl_{}".format(i)).uc()
|
||||
sense_amp_br = self.sense_amp_array_inst.get_pin("br_{}".format(i)).uc()
|
||||
|
||||
if self.col_addr_size>0:
|
||||
# Sense amp is connected to the col mux
|
||||
connect_bl = self.col_mux_array_inst.get_pin("bl_out[{}]".format(i)).bc()
|
||||
connect_br = self.col_mux_array_inst.get_pin("br_out[{}]".format(i)).bc()
|
||||
connect_bl = self.col_mux_array_inst.get_pin("bl_out_{}".format(i)).bc()
|
||||
connect_br = self.col_mux_array_inst.get_pin("br_out_{}".format(i)).bc()
|
||||
else:
|
||||
# Sense amp is directly connected to the bitcell array
|
||||
connect_bl = self.bitcell_array_inst.get_pin("bl[{}]".format(i)).bc()
|
||||
connect_br = self.bitcell_array_inst.get_pin("br[{}]".format(i)).bc()
|
||||
connect_bl = self.bitcell_array_inst.get_pin("bl_{}".format(i)).bc()
|
||||
connect_br = self.bitcell_array_inst.get_pin("br_{}".format(i)).bc()
|
||||
|
||||
|
||||
yoffset = 0.5*(sense_amp_bl.y+connect_bl.y)
|
||||
|
|
@ -609,8 +587,8 @@ class multibank(design.design):
|
|||
|
||||
for i in range(self.word_size):
|
||||
# Connection of data_out of sense amp to data_in
|
||||
tri_gate_in = self.tri_gate_array_inst.get_pin("in[{}]".format(i)).lc()
|
||||
sa_data_out = self.sense_amp_array_inst.get_pin("data[{}]".format(i)).bc()
|
||||
tri_gate_in = self.tri_gate_array_inst.get_pin("in_{}".format(i)).lc()
|
||||
sa_data_out = self.sense_amp_array_inst.get_pin("data_{}".format(i)).bc()
|
||||
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=tri_gate_in)
|
||||
|
|
@ -621,8 +599,8 @@ class multibank(design.design):
|
|||
def route_sense_amp_out(self):
|
||||
""" Add pins for the sense amp output """
|
||||
for i in range(self.word_size):
|
||||
data_pin = self.sense_amp_array_inst.get_pin("data[{}]".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
||||
data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||
layer=data_pin.layer,
|
||||
offset=data_pin.center(),
|
||||
height=data_pin.height(),
|
||||
|
|
@ -631,8 +609,8 @@ class multibank(design.design):
|
|||
def route_tri_gate_out(self):
|
||||
""" Metal 3 routing of tri_gate output data """
|
||||
for i in range(self.word_size):
|
||||
data_pin = self.tri_gate_array_inst.get_pin("out[{}]".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT[{}]".format(i),
|
||||
data_pin = self.tri_gate_array_inst.get_pin("out_{}".format(i))
|
||||
self.add_layout_pin_rect_center(text="DOUT_{}".format(i),
|
||||
layer=data_pin.layer,
|
||||
offset=data_pin.center(),
|
||||
height=data_pin.height(),
|
||||
|
|
@ -645,8 +623,8 @@ class multibank(design.design):
|
|||
# Create inputs for the row address lines
|
||||
for i in range(self.row_addr_size):
|
||||
addr_idx = i + self.col_addr_size
|
||||
decoder_name = "A[{}]".format(i)
|
||||
addr_name = "A[{}]".format(addr_idx)
|
||||
decoder_name = "A_{}".format(i)
|
||||
addr_name = "A_{}".format(addr_idx)
|
||||
self.copy_layout_pin(self.row_decoder_inst, decoder_name, addr_name)
|
||||
|
||||
|
||||
|
|
@ -654,8 +632,8 @@ class multibank(design.design):
|
|||
""" Connecting write driver """
|
||||
|
||||
for i in range(self.word_size):
|
||||
data_name = "data[{}]".format(i)
|
||||
din_name = "BANK_DIN[{}]".format(i)
|
||||
data_name = "data_{}".format(i)
|
||||
din_name = "BANK_DIN_{}".format(i)
|
||||
self.copy_layout_pin(self.write_driver_array_inst, data_name, din_name)
|
||||
|
||||
|
||||
|
|
@ -666,15 +644,15 @@ class multibank(design.design):
|
|||
# we don't care about bends after connecting to the input pin, so let the path code decide.
|
||||
for i in range(self.num_rows):
|
||||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode[{}]".format(i)).rc()
|
||||
driver_in_pos = self.wordline_driver_inst.get_pin("in[{}]".format(i)).lc()
|
||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(i)).rc()
|
||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(i)).lc()
|
||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||
self.add_path("metal1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
|
||||
# The mid guarantees we exit the input cell to the right.
|
||||
driver_wl_pos = self.wordline_driver_inst.get_pin("wl[{}]".format(i)).rc()
|
||||
bitcell_wl_pos = self.bitcell_array_inst.get_pin("wl[{}]".format(i)).lc()
|
||||
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])
|
||||
|
|
@ -699,20 +677,20 @@ class multibank(design.design):
|
|||
elif self.col_addr_size > 1:
|
||||
decode_names = []
|
||||
for i in range(self.num_col_addr_lines):
|
||||
decode_names.append("out[{}]".format(i))
|
||||
decode_names.append("out_{}".format(i))
|
||||
|
||||
for i in range(self.col_addr_size):
|
||||
decoder_name = "in[{}]".format(i)
|
||||
addr_name = "A[{}]".format(i)
|
||||
decoder_name = "in_{}".format(i)
|
||||
addr_name = "A_{}".format(i)
|
||||
self.copy_layout_pin(self.col_decoder_inst, decoder_name, addr_name)
|
||||
|
||||
|
||||
# This will do a quick "river route" on two layers.
|
||||
# When above the top select line it will offset "inward" again to prevent conflicts.
|
||||
# This could be done on a single layer, but we follow preferred direction rules for later routing.
|
||||
top_y_offset = self.col_mux_array_inst.get_pin("sel[{}]".format(self.num_col_addr_lines-1)).cy()
|
||||
top_y_offset = self.col_mux_array_inst.get_pin("sel_{}".format(self.num_col_addr_lines-1)).cy()
|
||||
for (decode_name,i) in zip(decode_names,range(self.num_col_addr_lines)):
|
||||
mux_name = "sel[{}]".format(i)
|
||||
mux_name = "sel_{}".format(i)
|
||||
mux_addr_pos = self.col_mux_array_inst.get_pin(mux_name).lc()
|
||||
|
||||
decode_out_pos = self.col_decoder_inst.get_pin(decode_name).center()
|
||||
|
|
@ -738,7 +716,7 @@ class multibank(design.design):
|
|||
"""
|
||||
# Add the wordline names
|
||||
for i in range(self.num_rows):
|
||||
wl_name = "wl[{}]".format(i)
|
||||
wl_name = "wl_{}".format(i)
|
||||
wl_pin = self.bitcell_array_inst.get_pin(wl_name)
|
||||
self.add_label(text=wl_name,
|
||||
layer="metal1",
|
||||
|
|
@ -746,8 +724,8 @@ class multibank(design.design):
|
|||
|
||||
# Add the bitline names
|
||||
for i in range(self.num_cols):
|
||||
bl_name = "bl[{}]".format(i)
|
||||
br_name = "br[{}]".format(i)
|
||||
bl_name = "bl_{}".format(i)
|
||||
br_name = "br_{}".format(i)
|
||||
bl_pin = self.bitcell_array_inst.get_pin(bl_name)
|
||||
br_pin = self.bitcell_array_inst.get_pin(br_name)
|
||||
self.add_label(text=bl_name,
|
||||
|
|
@ -759,16 +737,16 @@ class multibank(design.design):
|
|||
|
||||
# # Add the data output names to the sense amp output
|
||||
# for i in range(self.word_size):
|
||||
# data_name = "data[{}]".format(i)
|
||||
# data_name = "data_{}".format(i)
|
||||
# data_pin = self.sense_amp_array_inst.get_pin(data_name)
|
||||
# self.add_label(text="sa_out[{}]".format(i),
|
||||
# self.add_label(text="sa_out_{}".format(i),
|
||||
# layer="metal2",
|
||||
# offset=data_pin.center())
|
||||
|
||||
# Add labels on the decoder
|
||||
for i in range(self.word_size):
|
||||
data_name = "dec_out[{}]".format(i)
|
||||
pin_name = "in[{}]".format(i)
|
||||
data_name = "dec_out_{}".format(i)
|
||||
pin_name = "in_{}".format(i)
|
||||
data_pin = self.wordline_driver_inst.get_pin(pin_name)
|
||||
self.add_label(text=data_name,
|
||||
layer="metal1",
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ class precharge_array(design.design):
|
|||
def add_pins(self):
|
||||
"""Adds pins for spice file"""
|
||||
for i in range(self.columns):
|
||||
self.add_pin("bl[{0}]".format(i))
|
||||
self.add_pin("br[{0}]".format(i))
|
||||
self.add_pin("en")
|
||||
self.add_pin("bl_{0}".format(i))
|
||||
self.add_pin("br_{0}".format(i))
|
||||
self.add_pin("en_bar")
|
||||
self.add_pin("vdd")
|
||||
|
||||
def create_netlist(self):
|
||||
|
|
@ -59,11 +59,11 @@ class precharge_array(design.design):
|
|||
|
||||
def add_layout_pins(self):
|
||||
|
||||
self.add_layout_pin(text="en",
|
||||
self.add_layout_pin(text="en_bar",
|
||||
layer="metal1",
|
||||
offset=self.pc_cell.get_pin("en").ll(),
|
||||
offset=self.pc_cell.get_pin("en_bar").ll(),
|
||||
width=self.width,
|
||||
height=drc["minwidth_metal1"])
|
||||
height=drc("minwidth_metal1"))
|
||||
|
||||
for inst in self.local_insts:
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
|
|
@ -71,16 +71,16 @@ class precharge_array(design.design):
|
|||
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),
|
||||
self.add_layout_pin(text="bl_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=drc["minwidth_metal2"],
|
||||
width=drc("minwidth_metal2"),
|
||||
height=bl_pin.height())
|
||||
br_pin = inst.get_pin("br")
|
||||
self.add_layout_pin(text="br[{0}]".format(i),
|
||||
self.add_layout_pin(text="br_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=br_pin.ll(),
|
||||
width=drc["minwidth_metal2"],
|
||||
width=drc("minwidth_metal2"),
|
||||
height=bl_pin.height())
|
||||
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ class precharge_array(design.design):
|
|||
mod=self.pc_cell,
|
||||
offset=offset)
|
||||
self.local_insts.append(inst)
|
||||
self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i), "en", "vdd"])
|
||||
self.connect_inst(["bl_{0}".format(i), "br_{0}".format(i), "en_bar", "vdd"])
|
||||
|
||||
|
||||
def place_insts(self):
|
||||
|
|
@ -102,3 +102,9 @@ class precharge_array(design.design):
|
|||
for i in range(self.columns):
|
||||
offset = vector(self.pc_cell.width * i, 0)
|
||||
self.local_insts[i].place(offset)
|
||||
|
||||
def get_en_cin(self):
|
||||
"""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
|
||||
|
|
@ -15,12 +15,11 @@ class replica_bitline(design.design):
|
|||
line and rows is the height of the replica bit loads.
|
||||
"""
|
||||
|
||||
def __init__(self, delay_stages, delay_fanout, bitcell_loads, name="replica_bitline"):
|
||||
def __init__(self, delay_fanout_list, bitcell_loads, name="replica_bitline"):
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.bitcell_loads = bitcell_loads
|
||||
self.delay_stages = delay_stages
|
||||
self.delay_fanout = delay_fanout
|
||||
self.delay_fanout_list = delay_fanout_list
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
@ -29,11 +28,11 @@ class replica_bitline(design.design):
|
|||
def create_netlist(self):
|
||||
self.add_modules()
|
||||
self.add_pins()
|
||||
self.create_modules()
|
||||
self.create_instances()
|
||||
|
||||
def create_layout(self):
|
||||
self.calculate_module_offsets()
|
||||
self.place_modules()
|
||||
self.place_instances()
|
||||
self.route()
|
||||
self.add_layout_pins()
|
||||
|
||||
|
|
@ -76,13 +75,12 @@ class replica_bitline(design.design):
|
|||
self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height)
|
||||
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
""" Add the modules for later usage """
|
||||
|
||||
from importlib import reload
|
||||
g = reload(__import__(OPTS.delay_chain))
|
||||
self.mod_delay_chain = getattr(g, OPTS.delay_chain)
|
||||
#g = reload(__import__(OPTS.delay_chain))
|
||||
#self.mod_delay_chain = getattr(g, OPTS.delay_chain)
|
||||
|
||||
g = reload(__import__(OPTS.replica_bitcell))
|
||||
self.mod_replica_bitcell = getattr(g, OPTS.replica_bitcell)
|
||||
|
|
@ -91,11 +89,12 @@ class replica_bitline(design.design):
|
|||
self.add_mod(self.bitcell)
|
||||
|
||||
# This is the replica bitline load column that is the height of our array
|
||||
self.rbl = bitcell_array(name="bitline_load", cols=1, rows=self.bitcell_loads)
|
||||
self.rbl = bitcell_array(cols=1, rows=self.bitcell_loads)
|
||||
self.add_mod(self.rbl)
|
||||
|
||||
# FIXME: The FO and depth of this should be tuned
|
||||
self.delay_chain = self.mod_delay_chain([self.delay_fanout]*self.delay_stages)
|
||||
from delay_chain import delay_chain
|
||||
self.delay_chain = delay_chain(self.delay_fanout_list)
|
||||
self.add_mod(self.delay_chain)
|
||||
|
||||
self.inv = pinv()
|
||||
|
|
@ -104,18 +103,18 @@ class replica_bitline(design.design):
|
|||
self.access_tx = ptx(tx_type="pmos")
|
||||
self.add_mod(self.access_tx)
|
||||
|
||||
def create_modules(self):
|
||||
def create_instances(self):
|
||||
""" Create all of the module instances in the logical netlist """
|
||||
|
||||
# This is the threshold detect inverter on the output of the RBL
|
||||
self.rbl_inv_inst=self.add_inst(name="rbl_inv",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["bl[0]", "out", "vdd", "gnd"])
|
||||
self.connect_inst(["bl0_0", "out", "vdd", "gnd"])
|
||||
|
||||
self.tx_inst=self.add_inst(name="rbl_access_tx",
|
||||
mod=self.access_tx)
|
||||
# D, G, S, B
|
||||
self.connect_inst(["vdd", "delayed_en", "bl[0]", "vdd"])
|
||||
self.connect_inst(["vdd", "delayed_en", "bl0_0", "vdd"])
|
||||
# add the well and poly contact
|
||||
|
||||
self.dc_inst=self.add_inst(name="delay_chain",
|
||||
|
|
@ -124,28 +123,35 @@ class replica_bitline(design.design):
|
|||
|
||||
self.rbc_inst=self.add_inst(name="bitcell",
|
||||
mod=self.replica_bitcell)
|
||||
self.connect_inst(["bl[0]", "br[0]", "delayed_en", "vdd", "gnd"])
|
||||
temp = []
|
||||
for port in self.all_ports:
|
||||
temp.append("bl{}_0".format(port))
|
||||
temp.append("br{}_0".format(port))
|
||||
for port in self.all_ports:
|
||||
temp.append("delayed_en")
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
#self.connect_inst(["bl_0", "br_0", "delayed_en", "vdd", "gnd"])
|
||||
|
||||
self.rbl_inst=self.add_inst(name="load",
|
||||
mod=self.rbl)
|
||||
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
temp = []
|
||||
temp.append("bl[0]")
|
||||
temp.append("br[0]")
|
||||
for port in range(total_ports - 1):
|
||||
temp.append("gnd")
|
||||
temp.append("gnd")
|
||||
for port in self.all_ports:
|
||||
temp.append("bl{}_0".format(port))
|
||||
temp.append("br{}_0".format(port))
|
||||
for wl in range(self.bitcell_loads):
|
||||
for port in range(total_ports):
|
||||
for port in self.all_ports:
|
||||
temp.append("gnd")
|
||||
temp.append("vdd")
|
||||
temp.append("gnd")
|
||||
self.connect_inst(temp)
|
||||
|
||||
self.wl_list = self.rbl.cell.list_all_wl_names()
|
||||
self.bl_list = self.rbl.cell.list_all_bl_names()
|
||||
|
||||
def place_modules(self):
|
||||
def place_instances(self):
|
||||
""" Add all of the module instances in the logical netlist """
|
||||
|
||||
# This is the threshold detect inverter on the output of the RBL
|
||||
|
|
@ -160,9 +166,6 @@ class replica_bitline(design.design):
|
|||
mirror="MX")
|
||||
|
||||
self.rbl_inst.place(self.rbl_offset)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def route(self):
|
||||
|
|
@ -175,19 +178,76 @@ class replica_bitline(design.design):
|
|||
""" Connect the RBL word lines to gnd """
|
||||
# Connect the WL and gnd pins directly to the center and right gnd rails
|
||||
for row in range(self.bitcell_loads):
|
||||
wl = self.wl_list[0]+"[{}]".format(row)
|
||||
wl = self.wl_list[0]+"_{}".format(row)
|
||||
pin = self.rbl_inst.get_pin(wl)
|
||||
|
||||
# Route the connection to the right so that it doesn't interfere
|
||||
# with the cells
|
||||
# Route the connection to the right so that it doesn't interfere with the cells
|
||||
# Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions
|
||||
pin_right = pin.rc()
|
||||
pin_extension = pin_right + vector(self.m1_pitch,0)
|
||||
pin_extension = pin_right + vector(self.m3_pitch,0)
|
||||
|
||||
if pin.layer != "metal1":
|
||||
continue
|
||||
self.add_path("metal1", [pin_right, pin_extension])
|
||||
pin_width_ydir = pin.uy()-pin.by()
|
||||
#Width is set to pin y width to avoid DRC issues with m1 gaps
|
||||
self.add_path("metal1", [pin_right, pin_extension], pin_width_ydir)
|
||||
self.add_power_pin("gnd", pin_extension)
|
||||
|
||||
|
||||
|
||||
# for multiport, need to short wordlines to each other so they all connect to gnd.
|
||||
wl_last = self.wl_list[-1]+"_{}".format(row)
|
||||
pin_last = self.rbl_inst.get_pin(wl_last)
|
||||
self.short_wordlines(pin, pin_last, "right", False, row, vector(self.m3_pitch,0))
|
||||
|
||||
def short_wordlines(self, wl_pin_a, wl_pin_b, pin_side, is_replica_cell, cell_row=0, offset_x_vec=None):
|
||||
"""Connects the word lines together for a single bitcell. Also requires which side of the bitcell to short the pins."""
|
||||
#Assumes input pins are wordlines. Also assumes the word lines are horizontal in metal1. Also assumes pins have same x coord.
|
||||
#This is my (Hunter) first time editing layout in openram so this function is likely not optimal.
|
||||
if len(self.all_ports) > 1:
|
||||
#1. Create vertical metal for all the bitlines to connect to
|
||||
#m1 needs to be extended in the y directions, direction needs to be determined as every other cell is flipped
|
||||
correct_y = vector(0, 0.5*drc("minwidth_metal1"))
|
||||
#x spacing depends on the side being drawn. Unknown to me (Hunter) why the size of the space differs by the side.
|
||||
#I assume this is related to how a wire is draw, but I have not investigated the issue.
|
||||
if pin_side == "right":
|
||||
correct_x = vector(0.5*drc("minwidth_metal1"), 0)
|
||||
if offset_x_vec != None:
|
||||
correct_x = offset_x_vec
|
||||
else:
|
||||
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
|
||||
|
||||
if wl_pin_a.uy() > wl_pin_b.uy():
|
||||
self.add_path("metal1", [wl_pin_a.rc()+correct_x+correct_y, wl_pin_b.rc()+correct_x-correct_y])
|
||||
else:
|
||||
self.add_path("metal1", [wl_pin_a.rc()+correct_x-correct_y, wl_pin_b.rc()+correct_x+correct_y])
|
||||
elif pin_side == "left":
|
||||
if offset_x_vec != None:
|
||||
correct_x = offset_x_vec
|
||||
else:
|
||||
correct_x = vector(1.5*drc("minwidth_metal1"), 0)
|
||||
|
||||
if wl_pin_a.uy() > wl_pin_b.uy():
|
||||
self.add_path("metal1", [wl_pin_a.lc()-correct_x+correct_y, wl_pin_b.lc()-correct_x-correct_y])
|
||||
else:
|
||||
self.add_path("metal1", [wl_pin_a.lc()-correct_x-correct_y, wl_pin_b.lc()-correct_x+correct_y])
|
||||
else:
|
||||
debug.error("Could not connect wordlines on specified input side={}".format(pin_side),1)
|
||||
|
||||
#2. Connect word lines horizontally. Only replica cell needs. Bitline loads currently already do this.
|
||||
for port in self.all_ports:
|
||||
if is_replica_cell:
|
||||
wl = self.wl_list[port]
|
||||
pin = self.rbc_inst.get_pin(wl)
|
||||
else:
|
||||
wl = self.wl_list[port]+"_{}".format(cell_row)
|
||||
pin = self.rbl_inst.get_pin(wl)
|
||||
|
||||
if pin_side == "left":
|
||||
self.add_path("metal1", [pin.lc()-correct_x, pin.lc()])
|
||||
elif pin_side == "right":
|
||||
self.add_path("metal1", [pin.rc()+correct_x, pin.rc()])
|
||||
|
||||
|
||||
|
||||
def route_supplies(self):
|
||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
|
||||
|
|
@ -204,10 +264,8 @@ class replica_bitline(design.design):
|
|||
pin = self.rbl_inv_inst.get_pin("vdd")
|
||||
self.add_power_pin("vdd", pin.lc())
|
||||
|
||||
# Replica bitcell needs to be routed up to M3
|
||||
pin=self.rbc_inst.get_pin("vdd")
|
||||
# Don't rotate this via to vit in FreePDK45
|
||||
self.add_power_pin("vdd", pin.center(), rotate=0)
|
||||
self.add_power_pin("vdd", pin.center(), 0, pin.layer)
|
||||
|
||||
for pin in self.rbc_inst.get_pins("gnd"):
|
||||
self.add_power_pin("gnd", pin.center())
|
||||
|
|
@ -243,16 +301,28 @@ class replica_bitline(design.design):
|
|||
|
||||
# 3. Route the contact of previous route to the bitcell WL
|
||||
# route bend of previous net to bitcell WL
|
||||
wl_offset = self.rbc_inst.get_pin("wl").lc()
|
||||
xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
||||
wl_mid1 = vector(xmid_point,contact_offset.y)
|
||||
wl_mid2 = vector(xmid_point,wl_offset.y)
|
||||
self.add_path("metal1", [contact_offset, wl_mid1, wl_mid2, wl_offset])
|
||||
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
|
||||
wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0)
|
||||
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
|
||||
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
|
||||
#wl_mid1 = vector(xmid_point,contact_offset.y)
|
||||
#wl_mid2 = vector(xmid_point,wl_offset.y)
|
||||
self.add_path("metal1", [wl_offset, wl_mid1, wl_mid2, contact_offset])
|
||||
|
||||
# 4. Short wodlines if multiport
|
||||
wl = self.wl_list[0]
|
||||
wl_last = self.wl_list[-1]
|
||||
pin = self.rbc_inst.get_pin(wl)
|
||||
pin_last = self.rbc_inst.get_pin(wl_last)
|
||||
x_offset = self.short_wordlines(pin, pin_last, "left", True)
|
||||
|
||||
#correct = vector(0.5*drc("minwidth_metal1"), 0)
|
||||
#self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
|
||||
|
||||
# DRAIN ROUTE
|
||||
# Route the drain to the vdd rail
|
||||
drain_offset = self.tx_inst.get_pin("D").center()
|
||||
self.add_power_pin("vdd", drain_offset)
|
||||
self.add_power_pin("vdd", drain_offset, rotate=0)
|
||||
|
||||
# SOURCE ROUTE
|
||||
# Route the drain to the RBL inverter input
|
||||
|
|
@ -262,7 +332,7 @@ class replica_bitline(design.design):
|
|||
|
||||
# Route the connection of the source route to the RBL bitline (left)
|
||||
# Via will go halfway down from the bitcell
|
||||
bl_offset = self.rbc_inst.get_pin("bl").bc()
|
||||
bl_offset = self.rbc_inst.get_pin(self.bl_list[0]).bc()
|
||||
# Route down a pitch so we can use M2 routing
|
||||
bl_down_offset = bl_offset - vector(0, self.m2_pitch)
|
||||
self.add_path("metal2",[source_offset, bl_down_offset, bl_offset])
|
||||
|
|
@ -386,7 +456,7 @@ class replica_bitline(design.design):
|
|||
|
||||
# Connect the WL and gnd pins directly to the center and right gnd rails
|
||||
for row in range(self.bitcell_loads):
|
||||
wl = self.wl_list[0]+"[{}]".format(row)
|
||||
wl = self.wl_list[0]+"_{}".format(row)
|
||||
pin = self.rbl_inst.get_pin(wl)
|
||||
if pin.layer != "metal1":
|
||||
continue
|
||||
|
|
@ -530,4 +600,35 @@ class replica_bitline(design.design):
|
|||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the enable input relative capacitance"""
|
||||
#The enable is only connected to the delay, get the cin from that module
|
||||
en_cin = self.delay_chain.get_cin()
|
||||
return en_cin
|
||||
|
||||
def determine_sen_stage_efforts(self, ext_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 = []
|
||||
#Stage 1 is the delay chain
|
||||
stage1_cout = self.get_delayed_en_cin()
|
||||
stage1 = self.delay_chain.determine_delayed_en_stage_efforts(stage1_cout, inp_is_rise)
|
||||
stage_effort_list += stage1
|
||||
|
||||
#There is a disconnect between the delay chain and inverter. The rise/fall of the input to the inverter
|
||||
#Will be the negation of the previous stage.
|
||||
last_stage_is_rise = not stage_effort_list[-1].is_rise
|
||||
|
||||
#The delay chain triggers the enable on the replica bitline (rbl). This is used to track the bitline delay whereas this
|
||||
#model is intended to track every but that. Therefore, the next stage is the inverter after the rbl.
|
||||
stage2 = self.inv.get_effort_stage(ext_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage2)
|
||||
|
||||
return stage_effort_list
|
||||
|
||||
def get_delayed_en_cin(self):
|
||||
"""Get the fanout capacitance (relative) of the delayed enable from the delay chain."""
|
||||
access_tx_cin = self.access_tx.get_cin()
|
||||
rbc_cin = self.replica_bitcell.get_wl_cin()
|
||||
return access_tx_cin + rbc_cin
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer
|
||||
from tech import GDS,layer, parameter,drc
|
||||
|
||||
class sense_amp(design.design):
|
||||
"""
|
||||
|
|
@ -13,7 +13,7 @@ class sense_amp(design.design):
|
|||
|
||||
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
|
|
@ -23,6 +23,14 @@ class sense_amp(design.design):
|
|||
self.height = sense_amp.height
|
||||
self.pin_map = sense_amp.pin_map
|
||||
|
||||
def input_load(self):
|
||||
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
from tech import spice, parameter
|
||||
# Default is 8x. Per Samira and Hodges-Jackson book:
|
||||
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
|
||||
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||
return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
from tech import spice
|
||||
r = spice["min_tx_r"]/(10)
|
||||
|
|
@ -35,3 +43,9 @@ class sense_amp(design.design):
|
|||
#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")
|
||||
return 2*pmos_cin + nmos_cin
|
||||
|
|
@ -43,9 +43,9 @@ class sense_amp_array(design.design):
|
|||
|
||||
def add_pins(self):
|
||||
for i in range(0,self.word_size):
|
||||
self.add_pin("data[{0}]".format(i))
|
||||
self.add_pin("bl[{0}]".format(i))
|
||||
self.add_pin("br[{0}]".format(i))
|
||||
self.add_pin("data_{0}".format(i))
|
||||
self.add_pin("bl_{0}".format(i))
|
||||
self.add_pin("br_{0}".format(i))
|
||||
self.add_pin("en")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
|
@ -70,9 +70,9 @@ class sense_amp_array(design.design):
|
|||
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),
|
||||
self.connect_inst(["bl_{0}".format(i),
|
||||
"br_{0}".format(i),
|
||||
"data_{0}".format(i),
|
||||
"en", "vdd", "gnd"])
|
||||
|
||||
def place_sense_amp_array(self):
|
||||
|
|
@ -107,18 +107,18 @@ class sense_amp_array(design.design):
|
|||
br_pin = inst.get_pin("br")
|
||||
dout_pin = inst.get_pin("dout")
|
||||
|
||||
self.add_layout_pin(text="bl[{0}]".format(i),
|
||||
self.add_layout_pin(text="bl_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=bl_pin.width(),
|
||||
height=bl_pin.height())
|
||||
self.add_layout_pin(text="br[{0}]".format(i),
|
||||
self.add_layout_pin(text="br_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=br_pin.ll(),
|
||||
width=br_pin.width(),
|
||||
height=br_pin.height())
|
||||
|
||||
self.add_layout_pin(text="data[{0}]".format(i),
|
||||
self.add_layout_pin(text="data_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=dout_pin.ll(),
|
||||
width=dout_pin.width(),
|
||||
|
|
@ -132,8 +132,15 @@ class sense_amp_array(design.design):
|
|||
layer="metal1",
|
||||
offset=sclk_offset,
|
||||
width=self.width,
|
||||
height=drc["minwidth_metal1"])
|
||||
height=drc("minwidth_metal1"))
|
||||
|
||||
def input_load(self):
|
||||
return self.amp.input_load()
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
return self.amp.analytical_delay(slew=slew, load=load)
|
||||
|
||||
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.words_per_row
|
||||
|
|
@ -50,19 +50,18 @@ class single_level_column_mux_array(design.design):
|
|||
|
||||
def add_pins(self):
|
||||
for i in range(self.columns):
|
||||
self.add_pin("bl[{}]".format(i))
|
||||
self.add_pin("br[{}]".format(i))
|
||||
self.add_pin("bl_{}".format(i))
|
||||
self.add_pin("br_{}".format(i))
|
||||
for i in range(self.words_per_row):
|
||||
self.add_pin("sel[{}]".format(i))
|
||||
self.add_pin("sel_{}".format(i))
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("bl_out[{}]".format(i))
|
||||
self.add_pin("br_out[{}]".format(i))
|
||||
self.add_pin("bl_out_{}".format(i))
|
||||
self.add_pin("br_out_{}".format(i))
|
||||
self.add_pin("gnd")
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
# FIXME: Why is this 8x?
|
||||
self.mux = single_level_column_mux(tx_size=8, bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br)
|
||||
self.mux = single_level_column_mux(bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br)
|
||||
self.add_mod(self.mux)
|
||||
|
||||
|
||||
|
|
@ -83,11 +82,11 @@ class single_level_column_mux_array(design.design):
|
|||
self.mux_inst.append(self.add_inst(name=name,
|
||||
mod=self.mux))
|
||||
|
||||
self.connect_inst(["bl[{}]".format(col_num),
|
||||
"br[{}]".format(col_num),
|
||||
"bl_out[{}]".format(int(col_num/self.words_per_row)),
|
||||
"br_out[{}]".format(int(col_num/self.words_per_row)),
|
||||
"sel[{}]".format(col_num % self.words_per_row),
|
||||
self.connect_inst(["bl_{}".format(col_num),
|
||||
"br_{}".format(col_num),
|
||||
"bl_out_{}".format(int(col_num/self.words_per_row)),
|
||||
"br_out_{}".format(int(col_num/self.words_per_row)),
|
||||
"sel_{}".format(col_num % self.words_per_row),
|
||||
"gnd"])
|
||||
|
||||
def place_array(self):
|
||||
|
|
@ -104,13 +103,13 @@ class single_level_column_mux_array(design.design):
|
|||
for col_num in range(self.columns):
|
||||
mux_inst = self.mux_inst[col_num]
|
||||
offset = mux_inst.get_pin("bl").ll()
|
||||
self.add_layout_pin(text="bl[{}]".format(col_num),
|
||||
self.add_layout_pin(text="bl_{}".format(col_num),
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
height=self.height-offset.y)
|
||||
|
||||
offset = mux_inst.get_pin("br").ll()
|
||||
self.add_layout_pin(text="br[{}]".format(col_num),
|
||||
self.add_layout_pin(text="br_{}".format(col_num),
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
height=self.height-offset.y)
|
||||
|
|
@ -128,7 +127,7 @@ class single_level_column_mux_array(design.design):
|
|||
""" Create address input rails on M1 below the mux transistors """
|
||||
for j in range(self.words_per_row):
|
||||
offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch)
|
||||
self.add_layout_pin(text="sel[{}]".format(j),
|
||||
self.add_layout_pin(text="sel_{}".format(j),
|
||||
layer="metal1",
|
||||
offset=offset,
|
||||
width=self.mux.width * self.columns,
|
||||
|
|
@ -144,9 +143,9 @@ class single_level_column_mux_array(design.design):
|
|||
# Add the column x offset to find the right select bit
|
||||
gate_offset = self.mux_inst[col].get_pin("sel").bc()
|
||||
# height to connect the gate to the correct horizontal row
|
||||
sel_height = self.get_pin("sel[{}]".format(sel_index)).by()
|
||||
sel_height = self.get_pin("sel_{}".format(sel_index)).by()
|
||||
# use the y offset from the sel pin and the x offset from the gate
|
||||
offset = vector(gate_offset.x,self.get_pin("sel[{}]".format(sel_index)).cy())
|
||||
offset = vector(gate_offset.x,self.get_pin("sel_{}".format(sel_index)).cy())
|
||||
# Add the poly contact with a shift to account for the rotation
|
||||
self.add_via_center(layers=("metal1", "contact", "poly"),
|
||||
offset=offset,
|
||||
|
|
@ -170,23 +169,23 @@ class single_level_column_mux_array(design.design):
|
|||
self.add_rect(layer="metal1",
|
||||
offset=bl_out_offset,
|
||||
width=width,
|
||||
height=drc["minwidth_metal2"])
|
||||
height=drc("minwidth_metal2"))
|
||||
self.add_rect(layer="metal1",
|
||||
offset=br_out_offset,
|
||||
width=width,
|
||||
height=drc["minwidth_metal2"])
|
||||
height=drc("minwidth_metal2"))
|
||||
|
||||
|
||||
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
|
||||
self.add_layout_pin(text="bl_out[{}]".format(int(j/self.words_per_row)),
|
||||
self.add_layout_pin(text="bl_out_{}".format(int(j/self.words_per_row)),
|
||||
layer="metal2",
|
||||
offset=bl_out_offset.scale(1,0),
|
||||
width=drc['minwidth_metal2'],
|
||||
width=drc('minwidth_metal2'),
|
||||
height=self.route_height)
|
||||
self.add_layout_pin(text="br_out[{}]".format(int(j/self.words_per_row)),
|
||||
self.add_layout_pin(text="br_out_{}".format(int(j/self.words_per_row)),
|
||||
layer="metal2",
|
||||
offset=br_out_offset.scale(1,0),
|
||||
width=drc['minwidth_metal2'],
|
||||
width=drc('minwidth_metal2'),
|
||||
height=self.route_height)
|
||||
|
||||
# This via is on the right of the wire
|
||||
|
|
@ -202,7 +201,7 @@ class single_level_column_mux_array(design.design):
|
|||
|
||||
self.add_rect(layer="metal2",
|
||||
offset=bl_out_offset,
|
||||
width=drc['minwidth_metal2'],
|
||||
width=drc('minwidth_metal2'),
|
||||
height=self.route_height-bl_out_offset.y)
|
||||
# This via is on the right of the wire
|
||||
self.add_via(layers=("metal1", "via1", "metal2"),
|
||||
|
|
@ -210,12 +209,20 @@ class single_level_column_mux_array(design.design):
|
|||
rotate=90)
|
||||
self.add_rect(layer="metal2",
|
||||
offset=br_out_offset,
|
||||
width=drc['minwidth_metal2'],
|
||||
width=drc('minwidth_metal2'),
|
||||
height=self.route_height-br_out_offset.y)
|
||||
# This via is on the left of the wire
|
||||
self.add_via(layers=("metal1", "via1", "metal2"),
|
||||
offset= br_out_offset,
|
||||
rotate=90)
|
||||
|
||||
|
||||
def analytical_delay(self, vdd, slew, load=0.0):
|
||||
from tech import spice, parameter
|
||||
r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"])
|
||||
#Drains of mux transistors make up capacitance.
|
||||
c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff
|
||||
volt_swing = spice["v_threshold_typical"]/vdd
|
||||
|
||||
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = volt_swing)
|
||||
return self.return_delay(result.delay, result.slew)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class tri_gate(design.design):
|
|||
|
||||
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
|
||||
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"])
|
||||
|
||||
unique_id = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ class tri_gate_array(design.design):
|
|||
def add_pins(self):
|
||||
"""create the name of pins depend on the word size"""
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("in[{0}]".format(i))
|
||||
self.add_pin("in_{0}".format(i))
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("out[{0}]".format(i))
|
||||
self.add_pin("out_{0}".format(i))
|
||||
for pin in ["en", "en_bar", "vdd", "gnd"]:
|
||||
self.add_pin(pin)
|
||||
|
||||
|
|
@ -59,8 +59,8 @@ class tri_gate_array(design.design):
|
|||
self.tri_inst[i]=self.add_inst(name=name,
|
||||
mod=self.tri)
|
||||
index = int(i/self.words_per_row)
|
||||
self.connect_inst(["in[{0}]".format(index),
|
||||
"out[{0}]".format(index),
|
||||
self.connect_inst(["in_{0}".format(index),
|
||||
"out_{0}".format(index),
|
||||
"en", "en_bar", "vdd", "gnd"])
|
||||
|
||||
def place_array(self):
|
||||
|
|
@ -76,14 +76,14 @@ class tri_gate_array(design.design):
|
|||
index = int(i/self.words_per_row)
|
||||
|
||||
in_pin = self.tri_inst[i].get_pin("in")
|
||||
self.add_layout_pin(text="in[{0}]".format(index),
|
||||
self.add_layout_pin(text="in_{0}".format(index),
|
||||
layer="metal2",
|
||||
offset=in_pin.ll(),
|
||||
width=in_pin.width(),
|
||||
height=in_pin.height())
|
||||
|
||||
out_pin = self.tri_inst[i].get_pin("out")
|
||||
self.add_layout_pin(text="out[{0}]".format(index),
|
||||
self.add_layout_pin(text="out_{0}".format(index),
|
||||
layer="metal2",
|
||||
offset=out_pin.ll(),
|
||||
width=out_pin.width(),
|
||||
|
|
@ -107,14 +107,14 @@ class tri_gate_array(design.design):
|
|||
layer="metal1",
|
||||
offset=en_pin.ll().scale(0, 1),
|
||||
width=width,
|
||||
height=drc["minwidth_metal1"])
|
||||
height=drc("minwidth_metal1"))
|
||||
|
||||
enbar_pin = self.tri_inst[0].get_pin("en_bar")
|
||||
self.add_layout_pin(text="en_bar",
|
||||
layer="metal1",
|
||||
offset=enbar_pin.ll().scale(0, 1),
|
||||
width=width,
|
||||
height=drc["minwidth_metal1"])
|
||||
height=drc("minwidth_metal1"))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ class wordline_driver(design.design):
|
|||
def add_pins(self):
|
||||
# inputs to wordline_driver.
|
||||
for i in range(self.rows):
|
||||
self.add_pin("in[{0}]".format(i))
|
||||
self.add_pin("in_{0}".format(i))
|
||||
# Outputs from wordline_driver.
|
||||
for i in range(self.rows):
|
||||
self.add_pin("wl[{0}]".format(i))
|
||||
self.add_pin("en")
|
||||
self.add_pin("wl_{0}".format(i))
|
||||
self.add_pin("en_bar")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class wordline_driver(design.design):
|
|||
""" 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.inv1_inst[0].rx()
|
||||
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
|
||||
|
|
@ -95,59 +95,45 @@ class wordline_driver(design.design):
|
|||
|
||||
|
||||
def create_drivers(self):
|
||||
self.inv1_inst = []
|
||||
self.nand_inst = []
|
||||
self.inv2_inst = []
|
||||
for row in range(self.rows):
|
||||
name_inv1 = "wl_driver_inv_en{}".format(row)
|
||||
name_nand = "wl_driver_nand{}".format(row)
|
||||
name_inv2 = "wl_driver_inv{}".format(row)
|
||||
|
||||
# add inv1 based on the info above
|
||||
self.inv1_inst.append(self.add_inst(name=name_inv1,
|
||||
mod=self.inv_no_output))
|
||||
self.connect_inst(["en",
|
||||
"en_bar[{0}]".format(row),
|
||||
"vdd", "gnd"])
|
||||
# add nand 2
|
||||
self.nand_inst.append(self.add_inst(name=name_nand,
|
||||
mod=self.nand2))
|
||||
self.connect_inst(["en_bar[{0}]".format(row),
|
||||
"in[{0}]".format(row),
|
||||
"wl_bar[{0}]".format(row),
|
||||
self.connect_inst(["en_bar",
|
||||
"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),
|
||||
self.connect_inst(["wl_bar_{0}".format(row),
|
||||
"wl_{0}".format(row),
|
||||
"vdd", "gnd"])
|
||||
|
||||
|
||||
def place_drivers(self):
|
||||
inv1_xoffset = 2*self.m1_width + 5*self.m1_space
|
||||
nand2_xoffset = inv1_xoffset + self.inv.width
|
||||
nand2_xoffset = 2*self.m1_width + 5*self.m1_space
|
||||
inv2_xoffset = nand2_xoffset + self.nand2.width
|
||||
|
||||
self.width = inv2_xoffset + self.inv.height
|
||||
driver_height = self.inv.height
|
||||
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 = driver_height*(row + 1)
|
||||
y_offset = self.inv.height*(row + 1)
|
||||
inst_mirror = "MX"
|
||||
else:
|
||||
y_offset = driver_height*row
|
||||
y_offset = self.inv.height*row
|
||||
inst_mirror = "R0"
|
||||
|
||||
inv1_offset = [inv1_xoffset, y_offset]
|
||||
nand2_offset=[nand2_xoffset, y_offset]
|
||||
inv2_offset=[inv2_xoffset, y_offset]
|
||||
|
||||
# add inv1 based on the info above
|
||||
self.inv1_inst[row].place(offset=inv1_offset,
|
||||
mirror=inst_mirror)
|
||||
# add nand 2
|
||||
self.nand_inst[row].place(offset=nand2_offset,
|
||||
mirror=inst_mirror)
|
||||
|
|
@ -160,7 +146,7 @@ class wordline_driver(design.design):
|
|||
""" Route all of the signals """
|
||||
|
||||
# Wordline enable connection
|
||||
en_pin=self.add_layout_pin(text="en",
|
||||
en_pin=self.add_layout_pin(text="en_bar",
|
||||
layer="metal2",
|
||||
offset=[self.m1_width + 2*self.m1_space,0],
|
||||
width=self.m2_width,
|
||||
|
|
@ -168,12 +154,11 @@ class wordline_driver(design.design):
|
|||
|
||||
|
||||
for row in range(self.rows):
|
||||
inv1_inst = self.inv1_inst[row]
|
||||
nand_inst = self.nand_inst[row]
|
||||
inv2_inst = self.inv2_inst[row]
|
||||
|
||||
# en connection
|
||||
a_pin = inv1_inst.get_pin("A")
|
||||
# en_bar 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",
|
||||
|
|
@ -182,13 +167,6 @@ class wordline_driver(design.design):
|
|||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=clk_offset)
|
||||
|
||||
# first inv to nand2 A
|
||||
zb_pos = inv1_inst.get_pin("Z").bc()
|
||||
zu_pos = inv1_inst.get_pin("Z").uc()
|
||||
bl_pos = nand_inst.get_pin("A").lc()
|
||||
br_pos = nand_inst.get_pin("A").rc()
|
||||
self.add_path("metal1", [zb_pos, zu_pos, bl_pos, br_pos])
|
||||
|
||||
# Nand2 out to 2nd inv
|
||||
zr_pos = nand_inst.get_pin("Z").rc()
|
||||
al_pos = inv2_inst.get_pin("A").lc()
|
||||
|
|
@ -205,7 +183,7 @@ class wordline_driver(design.design):
|
|||
input_offset = vector(0,b_pos.y + up_or_down)
|
||||
mid_via_offset = vector(clk_offset.x,input_offset.y) + vector(0.5*self.m2_width+self.m2_space+0.5*contact.m1m2.width,0)
|
||||
# must under the clk line in M1
|
||||
self.add_layout_pin_segment_center(text="in[{0}]".format(row),
|
||||
self.add_layout_pin_segment_center(text="in_{0}".format(row),
|
||||
layer="metal1",
|
||||
start=input_offset,
|
||||
end=mid_via_offset)
|
||||
|
|
@ -221,7 +199,7 @@ class wordline_driver(design.design):
|
|||
|
||||
# output each WL on the right
|
||||
wl_offset = inv2_inst.get_pin("Z").rc()
|
||||
self.add_layout_pin_segment_center(text="wl[{0}]".format(row),
|
||||
self.add_layout_pin_segment_center(text="wl_{0}".format(row),
|
||||
layer="metal1",
|
||||
start=wl_offset,
|
||||
end=wl_offset-vector(self.m1_width,0))
|
||||
|
|
@ -238,4 +216,25 @@ class wordline_driver(design.design):
|
|||
|
||||
|
||||
def input_load(self):
|
||||
"""Gets the capacitance of the wordline driver in absolute units (fF)"""
|
||||
return self.nand2.input_load()
|
||||
|
||||
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_effort_stage(stage1_cout, inp_is_rise)
|
||||
stage_effort_list.append(stage1)
|
||||
last_stage_is_rise = stage1.is_rise
|
||||
|
||||
stage2 = self.inv.get_effort_stage(external_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(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
|
||||
|
|
@ -13,7 +13,7 @@ class write_driver(design.design):
|
|||
|
||||
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
|
||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||
|
||||
def __init__(self, name):
|
||||
design.design.__init__(self, name)
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ class write_driver_array(design.design):
|
|||
|
||||
def add_pins(self):
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("data[{0}]".format(i))
|
||||
self.add_pin("data_{0}".format(i))
|
||||
for i in range(self.word_size):
|
||||
self.add_pin("bl[{0}]".format(i))
|
||||
self.add_pin("br[{0}]".format(i))
|
||||
self.add_pin("bl_{0}".format(i))
|
||||
self.add_pin("br_{0}".format(i))
|
||||
self.add_pin("en")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
|
@ -68,14 +68,14 @@ class write_driver_array(design.design):
|
|||
def create_write_array(self):
|
||||
self.driver_insts = {}
|
||||
for i in range(0,self.columns,self.words_per_row):
|
||||
name = "Xwrite_driver{}".format(i)
|
||||
name = "write_driver{}".format(i)
|
||||
index = int(i/self.words_per_row)
|
||||
self.driver_insts[index]=self.add_inst(name=name,
|
||||
mod=self.driver)
|
||||
|
||||
self.connect_inst(["data[{0}]".format(index),
|
||||
"bl[{0}]".format(index),
|
||||
"br[{0}]".format(index),
|
||||
self.connect_inst(["data_{0}".format(index),
|
||||
"bl_{0}".format(index),
|
||||
"br_{0}".format(index),
|
||||
"en", "vdd", "gnd"])
|
||||
|
||||
|
||||
|
|
@ -94,20 +94,20 @@ class write_driver_array(design.design):
|
|||
def add_layout_pins(self):
|
||||
for i in range(self.word_size):
|
||||
din_pin = self.driver_insts[i].get_pin("din")
|
||||
self.add_layout_pin(text="data[{0}]".format(i),
|
||||
self.add_layout_pin(text="data_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=din_pin.ll(),
|
||||
width=din_pin.width(),
|
||||
height=din_pin.height())
|
||||
bl_pin = self.driver_insts[i].get_pin("bl")
|
||||
self.add_layout_pin(text="bl[{0}]".format(i),
|
||||
self.add_layout_pin(text="bl_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=bl_pin.ll(),
|
||||
width=bl_pin.width(),
|
||||
height=bl_pin.height())
|
||||
|
||||
br_pin = self.driver_insts[i].get_pin("br")
|
||||
self.add_layout_pin(text="br[{0}]".format(i),
|
||||
self.add_layout_pin(text="br_{0}".format(i),
|
||||
layer="metal2",
|
||||
offset=br_pin.ll(),
|
||||
width=br_pin.width(),
|
||||
|
|
@ -130,7 +130,7 @@ class write_driver_array(design.design):
|
|||
layer="metal1",
|
||||
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=drc['minwidth_metal1'])
|
||||
height=drc('minwidth_metal1'))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ a spice (.sp) file for circuit simulation
|
|||
a GDS2 (.gds) file containing the layout
|
||||
a LEF (.lef) file for preliminary P&R (real one should be from layout)
|
||||
a Liberty (.lib) file for timing analysis/optimization
|
||||
|
||||
"""
|
||||
|
||||
import sys,os
|
||||
|
|
@ -27,36 +26,36 @@ if len(args) != 1:
|
|||
# These depend on arguments, so don't load them until now.
|
||||
import debug
|
||||
|
||||
|
||||
init_openram(config_file=args[0], is_unit_test=False)
|
||||
|
||||
# Only print banner here so it's not in unit tests
|
||||
print_banner()
|
||||
|
||||
# Output info about this run
|
||||
report_status()
|
||||
|
||||
# Start importing design modules after we have the config file
|
||||
import verify
|
||||
from sram import sram
|
||||
from sram_config import sram_config
|
||||
|
||||
output_extensions = ["sp","v","lib"]
|
||||
if not OPTS.netlist_only:
|
||||
output_extensions.extend(["gds","lef"])
|
||||
output_files = ["{0}.{1}".format(OPTS.output_name,x) for x in output_extensions]
|
||||
print("Output files are: ")
|
||||
print(*output_files,sep="\n")
|
||||
|
||||
# Keep track of running stats
|
||||
start_time = datetime.datetime.now()
|
||||
print_time("Start",start_time)
|
||||
|
||||
# Output info about this run
|
||||
report_status()
|
||||
|
||||
from sram_config import sram_config
|
||||
|
||||
|
||||
# Configure the SRAM organization
|
||||
c = sram_config(word_size=OPTS.word_size,
|
||||
num_words=OPTS.num_words)
|
||||
print("Words per row: {}".format(c.words_per_row))
|
||||
|
||||
# import SRAM test generation
|
||||
#from parser import *
|
||||
output_extensions = ["sp","v","lib","py","html"]
|
||||
if not OPTS.netlist_only:
|
||||
output_extensions.extend(["gds","lef"])
|
||||
output_files = ["{0}{1}.{2}".format(OPTS.output_path,OPTS.output_name,x) for x in output_extensions]
|
||||
print("Output files are: ")
|
||||
print(*output_files,sep="\n")
|
||||
|
||||
|
||||
from sram import sram
|
||||
s = sram(sram_config=c,
|
||||
name=OPTS.output_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ class options(optparse.Values):
|
|||
debug_level = 0
|
||||
# When enabled, layout is not generated (and no DRC or LVS are performed)
|
||||
netlist_only = False
|
||||
# This determines whether LVS and DRC is checked for each submodule.
|
||||
# This determines whether LVS and DRC is checked at all.
|
||||
check_lvsdrc = True
|
||||
# This determines whether LVS and DRC is checked for every submodule.
|
||||
inline_lvsdrc = False
|
||||
# Variable to select the variant of spice
|
||||
spice_name = ""
|
||||
# The spice executable being used which is derived from the user PATH.
|
||||
|
|
@ -50,8 +52,6 @@ class options(optparse.Values):
|
|||
analytical_delay = True
|
||||
# Purge the temp directory after a successful run (doesn't purge on errors, anyhow)
|
||||
purge_temp = True
|
||||
# Determines whether multi-port portion of unit tests are run or not
|
||||
multiport_check = True
|
||||
|
||||
# These are the configuration parameters
|
||||
num_rw_ports = 1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import debug
|
||||
from tech import drc
|
||||
from math import log
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from pnand2 import pnand2
|
||||
from pinv import pinv
|
||||
import pgate
|
||||
|
||||
class pand2(pgate.pgate):
|
||||
"""
|
||||
This is a simple buffer used for driving loads.
|
||||
"""
|
||||
from importlib import reload
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
bitcell = getattr(c, OPTS.bitcell)
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, size=1, height=None, name=""):
|
||||
|
||||
self.size = size
|
||||
|
||||
if name=="":
|
||||
name = "pand2_{0}_{1}".format(size, pand2.unique_id)
|
||||
pand2.unique_id += 1
|
||||
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
|
||||
|
||||
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):
|
||||
# Shield the cap, but have at least a stage effort of 4
|
||||
self.nand = pnand2(height=self.height)
|
||||
self.add_mod(self.nand)
|
||||
|
||||
self.inv = pinv(size=self.size, height=self.height)
|
||||
self.add_mod(self.inv)
|
||||
|
||||
def create_layout(self):
|
||||
self.width = self.nand.width + self.inv.width
|
||||
self.place_insts()
|
||||
self.add_wires()
|
||||
self.add_layout_pins()
|
||||
|
||||
def add_pins(self):
|
||||
self.add_pin("A")
|
||||
self.add_pin("B")
|
||||
self.add_pin("Z")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def create_insts(self):
|
||||
self.nand_inst=self.add_inst(name="pand2_nand",
|
||||
mod=self.nand)
|
||||
self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"])
|
||||
|
||||
self.inv_inst=self.add_inst(name="pand2_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 add_wires(self):
|
||||
# nand Z to inv A
|
||||
z1_pin = self.nand_inst.get_pin("Z")
|
||||
a2_pin = self.inv_inst.get_pin("A")
|
||||
mid1_point = vector(0.5*(z1_pin.cx()+a2_pin.cx()), z1_pin.cy())
|
||||
mid2_point = vector(mid1_point, a2_pin.cy())
|
||||
self.add_path("metal1", [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()])
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.inv_inst.get_pin("vdd")
|
||||
self.add_layout_pin(text="vdd",
|
||||
layer="metal1",
|
||||
offset=vdd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.inv_inst.get_pin("gnd")
|
||||
self.add_layout_pin(text="gnd",
|
||||
layer="metal1",
|
||||
offset=gnd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
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 analytical_delay(self, slew, load=0.0):
|
||||
""" Calculate the analytical delay of DFF-> INV -> INV """
|
||||
nand_delay = selfnand.analytical_delay(slew=slew, load=self.inv.input_load())
|
||||
inv_delay = self.inv.analytical_delay(slew=nand_delay.slew, load=load)
|
||||
return nand_delay + inv_delay
|
||||
|
||||
def get_output_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_effort_stage(stage1_cout, inp_is_rise)
|
||||
stage_effort_list.append(stage1)
|
||||
last_stage_is_rise = stage1.is_rise
|
||||
|
||||
stage2 = self.inv.get_effort_stage(external_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage2)
|
||||
|
||||
return stage_effort_list
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,144 @@
|
|||
import debug
|
||||
from tech import drc
|
||||
from math import log
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from pinv import pinv
|
||||
import pgate
|
||||
|
||||
class pbuf(pgate.pgate):
|
||||
"""
|
||||
This is a simple buffer used for driving loads.
|
||||
"""
|
||||
from importlib import reload
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
bitcell = getattr(c, OPTS.bitcell)
|
||||
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, size=4, height=None, name=""):
|
||||
|
||||
self.stage_effort = 4
|
||||
self.size = size
|
||||
self.height = height
|
||||
|
||||
if name=="":
|
||||
name = "pbuf_{0}_{1}".format(self.size, pbuf.unique_id)
|
||||
pbuf.unique_id += 1
|
||||
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
debug.info(1, "creating {0} with size of {1}".format(self.name,self.size))
|
||||
|
||||
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_layout(self):
|
||||
self.width = self.inv1.width + self.inv2.width
|
||||
self.place_insts()
|
||||
self.add_wires()
|
||||
self.add_layout_pins()
|
||||
|
||||
def add_pins(self):
|
||||
self.add_pin("A")
|
||||
self.add_pin("Z")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def create_modules(self):
|
||||
# Shield the cap, but have at least a stage effort of 4
|
||||
input_size = max(1,int(self.size/self.stage_effort))
|
||||
self.inv1 = pinv(size=input_size, height=self.height)
|
||||
self.add_mod(self.inv1)
|
||||
|
||||
self.inv2 = pinv(size=self.size, height=self.height)
|
||||
self.add_mod(self.inv2)
|
||||
|
||||
def create_insts(self):
|
||||
self.inv1_inst=self.add_inst(name="buf_inv1",
|
||||
mod=self.inv1)
|
||||
self.connect_inst(["A", "zb_int", "vdd", "gnd"])
|
||||
|
||||
|
||||
self.inv2_inst=self.add_inst(name="buf_inv2",
|
||||
mod=self.inv2)
|
||||
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||
|
||||
def place_insts(self):
|
||||
# Add INV1 to the right
|
||||
self.inv1_inst.place(vector(0,0))
|
||||
|
||||
# Add INV2 to the right
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||
|
||||
|
||||
def add_wires(self):
|
||||
# inv1 Z to inv2 A
|
||||
z1_pin = self.inv1_inst.get_pin("Z")
|
||||
a2_pin = self.inv2_inst.get_pin("A")
|
||||
mid_point = vector(z1_pin.cx(), a2_pin.cy())
|
||||
self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()])
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.inv1_inst.get_pin("vdd")
|
||||
self.add_layout_pin(text="vdd",
|
||||
layer="metal1",
|
||||
offset=vdd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.inv1_inst.get_pin("gnd")
|
||||
self.add_layout_pin(text="gnd",
|
||||
layer="metal1",
|
||||
offset=gnd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
z_pin = self.inv2_inst.get_pin("Z")
|
||||
self.add_layout_pin_rect_center(text="Z",
|
||||
layer=z_pin.layer,
|
||||
offset=z_pin.center(),
|
||||
width=z_pin.width(),
|
||||
height=z_pin.height())
|
||||
|
||||
a_pin = self.inv1_inst.get_pin("A")
|
||||
self.add_layout_pin_rect_center(text="A",
|
||||
layer=a_pin.layer,
|
||||
offset=a_pin.center(),
|
||||
width=a_pin.width(),
|
||||
height=a_pin.height())
|
||||
|
||||
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
""" Calculate the analytical delay of DFF-> INV -> INV """
|
||||
inv1_delay = self.inv1.analytical_delay(slew=slew, load=self.inv2.input_load())
|
||||
inv2_delay = self.inv2.analytical_delay(slew=inv1_delay.slew, load=load)
|
||||
return inv1_delay + inv2_delay
|
||||
|
||||
def get_output_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||
"""Get the stage efforts of the A -> Z path"""
|
||||
stage_effort_list = []
|
||||
stage1_cout = self.inv2.get_cin()
|
||||
stage1 = self.inv1.get_effort_stage(stage1_cout, inp_is_rise)
|
||||
stage_effort_list.append(stage1)
|
||||
last_stage_is_rise = stage1.is_rise
|
||||
|
||||
stage2 = self.inv2.get_effort_stage(external_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage2)
|
||||
|
||||
return stage_effort_list
|
||||
|
||||
def get_cin(self):
|
||||
"""Returns the relative capacitance of the input"""
|
||||
input_cin = self.inv1.get_cin()
|
||||
return input_cin
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
import debug
|
||||
import pgate
|
||||
import math
|
||||
from tech import drc
|
||||
from math import log
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from pinv import pinv
|
||||
|
||||
class pdriver(pgate.pgate):
|
||||
"""
|
||||
This instantiates an even or odd number of inverters sized for driving a load.
|
||||
"""
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, neg_polarity=False, fanout_size=8, size_list = [], height=None, name=""):
|
||||
|
||||
self.stage_effort = 4
|
||||
self.height = height
|
||||
self.neg_polarity = neg_polarity
|
||||
self.size_list = size_list
|
||||
self.fanout_size = fanout_size
|
||||
|
||||
if len(self.size_list) > 0 and (self.fanout_size != 8 or self.neg_polarity):
|
||||
debug.error("Cannot specify both size_list and neg_polarity or fanout_size.", -1)
|
||||
|
||||
if name=="":
|
||||
name = "pdriver_{}".format(pdriver.unique_id)
|
||||
pdriver.unique_id += 1
|
||||
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
debug.info(1, "Creating {}".format(self.name))
|
||||
|
||||
self.compute_sizes()
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
||||
def compute_sizes(self):
|
||||
# size_list specified
|
||||
if len(self.size_list) > 0:
|
||||
if not len(self.size_list) % 2:
|
||||
neg_polarity = True
|
||||
self.num_inv = len(self.size_list)
|
||||
else:
|
||||
# find the number of stages
|
||||
#fanout_size is a unit inverter fanout, not a capacitance so c_in=1
|
||||
num_stages = max(1,int(round(log(self.fanout_size)/log(4))))
|
||||
|
||||
# find inv_num and compute sizes
|
||||
if self.neg_polarity:
|
||||
if (num_stages % 2 == 0): # if num_stages is even
|
||||
self.diff_polarity(num_stages=num_stages)
|
||||
else: # if num_stages is odd
|
||||
self.same_polarity(num_stages=num_stages)
|
||||
else: # positive polarity
|
||||
if (num_stages % 2 == 0):
|
||||
self.same_polarity(num_stages=num_stages)
|
||||
else:
|
||||
self.diff_polarity(num_stages=num_stages)
|
||||
|
||||
|
||||
def same_polarity(self, num_stages):
|
||||
self.calc_size_list = []
|
||||
self.num_inv = num_stages
|
||||
# compute sizes
|
||||
fanout_size_prev = self.fanout_size
|
||||
for x in range(self.num_inv-1,-1,-1):
|
||||
fanout_size_prev = int(round(fanout_size_prev/self.stage_effort))
|
||||
self.calc_size_list.append(fanout_size_prev)
|
||||
|
||||
|
||||
def diff_polarity(self, num_stages):
|
||||
self.calc_size_list = []
|
||||
# find which delay is smaller
|
||||
if (num_stages > 1):
|
||||
delay_below = ((num_stages-1)*(self.fanout_size**(1/num_stages-1))) + num_stages-1
|
||||
delay_above = ((num_stages+1)*(self.fanout_size**(1/num_stages+1))) + num_stages+1
|
||||
if (delay_above < delay_below):
|
||||
# recompute stage_effort for this delay
|
||||
self.num_inv = num_stages+1
|
||||
polarity_stage_effort = self.fanout_size**(1/self.num_inv)
|
||||
else:
|
||||
self.num_inv = num_stages-1
|
||||
polarity_stage_effort = self.fanout_size**(1/self.num_inv)
|
||||
else: # num_stages is 1, can't go to 0
|
||||
self.num_inv = num_stages+1
|
||||
polarity_stage_effort = self.fanout_size**(1/self.num_inv)
|
||||
|
||||
|
||||
# compute sizes
|
||||
fanout_size_prev = self.fanout_size
|
||||
for x in range(self.num_inv-1,-1,-1):
|
||||
fanout_size_prev = int(round(fanout_size_prev/polarity_stage_effort))
|
||||
self.calc_size_list.append(fanout_size_prev)
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
inv_list = []
|
||||
|
||||
self.add_pins()
|
||||
self.add_modules()
|
||||
self.create_insts()
|
||||
|
||||
def create_layout(self):
|
||||
self.width = self.num_inv * self.inv_list[0].width
|
||||
self.height = self.inv_list[0].height
|
||||
|
||||
self.place_modules()
|
||||
self.route_wires()
|
||||
self.add_layout_pins()
|
||||
|
||||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
self.add_pin("A")
|
||||
self.add_pin("Z")
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
|
||||
def add_modules(self):
|
||||
self.inv_list = []
|
||||
if len(self.size_list) > 0: # size list specified
|
||||
for x in range(len(self.size_list)):
|
||||
self.inv_list.append(pinv(size=self.size_list[x], height=self.height))
|
||||
self.add_mod(self.inv_list[x])
|
||||
else: # find inv sizes
|
||||
for x in range(len(self.calc_size_list)):
|
||||
self.inv_list.append(pinv(size=self.calc_size_list[x], height=self.height))
|
||||
self.add_mod(self.inv_list[x])
|
||||
|
||||
|
||||
def create_insts(self):
|
||||
self.inv_inst_list = []
|
||||
for x in range(1,self.num_inv+1):
|
||||
# Create first inverter
|
||||
if x == 1:
|
||||
zbx_int = "Zb{}_int".format(x);
|
||||
self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x),
|
||||
mod=self.inv_list[x-1]))
|
||||
if self.num_inv == 1:
|
||||
self.connect_inst(["A", "Z", "vdd", "gnd"])
|
||||
else:
|
||||
self.connect_inst(["A", zbx_int, "vdd", "gnd"])
|
||||
|
||||
# Create last inverter
|
||||
elif x == self.num_inv:
|
||||
zbn_int = "Zb{}_int".format(x-1);
|
||||
self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x),
|
||||
mod=self.inv_list[x-1]))
|
||||
self.connect_inst([zbn_int, "Z", "vdd", "gnd"])
|
||||
|
||||
# Create middle inverters
|
||||
else:
|
||||
zbx_int = "Zb{}_int".format(x-1);
|
||||
zbn_int = "Zb{}_int".format(x);
|
||||
self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x),
|
||||
mod=self.inv_list[x-1]))
|
||||
self.connect_inst([zbx_int, zbn_int, "vdd", "gnd"])
|
||||
|
||||
|
||||
def place_modules(self):
|
||||
# Add INV1 to the left
|
||||
self.inv_inst_list[0].place(vector(0,0))
|
||||
|
||||
# Add inverters to the right of INV1
|
||||
for x in range(1,len(self.inv_inst_list)):
|
||||
self.inv_inst_list[x].place(vector(self.inv_inst_list[x-1].rx(),0))
|
||||
|
||||
|
||||
def route_wires(self):
|
||||
z_inst_list = []
|
||||
a_inst_list = []
|
||||
# inv_current Z to inv_next A
|
||||
for x in range(0,len(self.inv_inst_list)-1):
|
||||
z_inst_list.append(self.inv_inst_list[x].get_pin("Z"))
|
||||
a_inst_list.append(self.inv_inst_list[x+1].get_pin("A"))
|
||||
mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy())
|
||||
self.add_path("metal1", [z_inst_list[x].center(), mid_point, a_inst_list[x].center()])
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.inv_inst_list[0].get_pin("vdd")
|
||||
self.add_layout_pin(text="vdd",
|
||||
layer="metal1",
|
||||
offset=vdd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.inv_inst_list[0].get_pin("gnd")
|
||||
self.add_layout_pin(text="gnd",
|
||||
layer="metal1",
|
||||
offset=gnd_pin.ll().scale(0,1),
|
||||
width=self.width,
|
||||
height=vdd_pin.height())
|
||||
|
||||
z_pin = self.inv_inst_list[len(self.inv_inst_list)-1].get_pin("Z")
|
||||
self.add_layout_pin_rect_center(text="Z",
|
||||
layer=z_pin.layer,
|
||||
offset=z_pin.center(),
|
||||
width = z_pin.width(),
|
||||
height = z_pin.height())
|
||||
|
||||
a_pin = self.inv_inst_list[0].get_pin("A")
|
||||
self.add_layout_pin_rect_center(text="A",
|
||||
layer=a_pin.layer,
|
||||
offset=a_pin.center(),
|
||||
width = a_pin.width(),
|
||||
height = a_pin.height())
|
||||
|
||||
def analytical_delay(self, slew, load=0.0):
|
||||
"""Calculate the analytical delay of INV1 -> ... -> INVn"""
|
||||
delay = 0;
|
||||
if len(self.inv_inst_list) == 1:
|
||||
delay = self.inv_inst_list[x].analytical_delay(slew=slew);
|
||||
else:
|
||||
for x in range(len(self.inv_inst_list-1)):
|
||||
load_next = 0.0
|
||||
for n in range(x,len(self.inv_inst_list+1)):
|
||||
load_next += self.inv_inst_list[x+1]
|
||||
if x == 1:
|
||||
delay += self.inv_inst_list[x].analytical_delay(slew=slew,
|
||||
load=load_next)
|
||||
else:
|
||||
delay += self.inv_inst_list[x+1].analytical_delay(slew=delay.slew,
|
||||
load=load_next)
|
||||
return delay
|
||||
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import contact
|
||||
import design
|
||||
import debug
|
||||
from tech import drc, parameter, spice, info
|
||||
from tech import drc, parameter, spice
|
||||
from ptx import ptx
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
|
|
@ -110,7 +110,7 @@ class pgate(design.design):
|
|||
max_y_offset = self.height + 0.5*self.m1_width
|
||||
self.nwell_position = middle_position
|
||||
nwell_height = max_y_offset - middle_position.y
|
||||
if info["has_nwell"]:
|
||||
if drc("has_nwell"):
|
||||
self.add_rect(layer="nwell",
|
||||
offset=middle_position,
|
||||
width=self.well_width,
|
||||
|
|
@ -122,7 +122,7 @@ class pgate(design.design):
|
|||
|
||||
pwell_position = vector(0,-0.5*self.m1_width)
|
||||
pwell_height = middle_position.y-pwell_position.y
|
||||
if info["has_pwell"]:
|
||||
if drc("has_pwell"):
|
||||
self.add_rect(layer="pwell",
|
||||
offset=pwell_position,
|
||||
width=self.well_width,
|
||||
|
|
@ -138,7 +138,7 @@ class pgate(design.design):
|
|||
layer_stack = ("active", "contact", "metal1")
|
||||
|
||||
# To the right a spacing away from the pmos right active edge
|
||||
contact_xoffset = pmos_pos.x + pmos.active_width + drc["active_to_body_active"]
|
||||
contact_xoffset = pmos_pos.x + pmos.active_width + drc("active_to_body_active")
|
||||
# Must be at least an well enclosure of active down from the top of the well
|
||||
# OR align the active with the top of PMOS active.
|
||||
max_y_offset = self.height + 0.5*self.m1_width
|
||||
|
|
@ -185,7 +185,7 @@ class pgate(design.design):
|
|||
pwell_position = vector(0,-0.5*self.m1_width)
|
||||
|
||||
# To the right a spacing away from the nmos right active edge
|
||||
contact_xoffset = nmos_pos.x + nmos.active_width + drc["active_to_body_active"]
|
||||
contact_xoffset = nmos_pos.x + nmos.active_width + drc("active_to_body_active")
|
||||
# Must be at least an well enclosure of active up from the bottom of the well
|
||||
contact_yoffset = max(nmos_pos.y,
|
||||
self.well_enclose_active - nmos.active_contact.first_layer_height/2)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import contact
|
||||
import pgate
|
||||
import debug
|
||||
from tech import drc, parameter, spice, info
|
||||
from tech import drc, parameter, spice
|
||||
from ptx import ptx
|
||||
from vector import vector
|
||||
from math import ceil
|
||||
from globals import OPTS
|
||||
from utils import round_to_grid
|
||||
import logical_effort
|
||||
|
||||
class pinv(pgate.pgate):
|
||||
"""
|
||||
|
|
@ -29,7 +30,8 @@ class pinv(pgate.pgate):
|
|||
pinv.unique_id += 1
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
debug.info(2, "create pinv structure {0} with size of {1}".format(name, size))
|
||||
|
||||
|
||||
self.size = size
|
||||
self.nmos_size = size
|
||||
self.pmos_size = beta*size
|
||||
self.beta = beta
|
||||
|
|
@ -76,8 +78,8 @@ class pinv(pgate.pgate):
|
|||
# This may make the result differ when the layout is created...
|
||||
if OPTS.netlist_only:
|
||||
self.tx_mults = 1
|
||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
||||
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||
return
|
||||
|
||||
# Do a quick sanity check and bail if unlikely feasible height
|
||||
|
|
@ -85,16 +87,16 @@ class pinv(pgate.pgate):
|
|||
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
|
||||
# plus the tx height
|
||||
nmos = ptx(tx_type="nmos")
|
||||
pmos = ptx(width=drc["minwidth_tx"], tx_type="pmos")
|
||||
pmos = ptx(width=drc("minwidth_tx"), tx_type="pmos")
|
||||
tx_height = nmos.poly_height + pmos.poly_height
|
||||
# rotated m1 pitch or poly to active spacing
|
||||
min_channel = max(contact.poly.width + self.m1_space,
|
||||
contact.poly.width + 2*drc["poly_to_active"])
|
||||
contact.poly.width + 2*drc("poly_to_active"))
|
||||
# This is the extra space needed to ensure DRC rules to the active contacts
|
||||
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
||||
# This is a poly-to-poly of a flipped cell
|
||||
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
|
||||
drc["poly_extend_active"], self.poly_space)
|
||||
drc("poly_extend_active"), self.poly_space)
|
||||
total_height = tx_height + min_channel + 2*self.top_bottom_space
|
||||
debug.check(self.height> total_height,"Cell height {0} too small for simple min height {1}.".format(self.height,total_height))
|
||||
|
||||
|
|
@ -103,16 +105,16 @@ class pinv(pgate.pgate):
|
|||
# Divide the height in half. Could divide proportional to beta, but this makes
|
||||
# connecting wells of multiple cells easier.
|
||||
# Subtract the poly space under the rail of the tx
|
||||
nmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
|
||||
pmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
|
||||
nmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
|
||||
pmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
|
||||
|
||||
debug.info(2,"Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available,
|
||||
nmos_height_available,
|
||||
pmos_height_available))
|
||||
|
||||
# Determine the number of mults for each to fit width into available space
|
||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
||||
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||
nmos_required_mults = max(int(ceil(self.nmos_width/nmos_height_available)),1)
|
||||
pmos_required_mults = max(int(ceil(self.pmos_width/pmos_height_available)),1)
|
||||
# The mults must be the same for easy connection of poly
|
||||
|
|
@ -124,9 +126,9 @@ class pinv(pgate.pgate):
|
|||
# We also need to round the width to the grid or we will end up with LVS property
|
||||
# mismatch errors when fingers are not a grid length and get rounded in the offset geometry.
|
||||
self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults)
|
||||
debug.check(self.nmos_width>=drc["minwidth_tx"],"Cannot finger NMOS transistors to fit cell height.")
|
||||
debug.check(self.nmos_width>=drc("minwidth_tx"),"Cannot finger NMOS transistors to fit cell height.")
|
||||
self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults)
|
||||
debug.check(self.pmos_width>=drc["minwidth_tx"],"Cannot finger PMOS transistors to fit cell height.")
|
||||
debug.check(self.pmos_width>=drc("minwidth_tx"),"Cannot finger PMOS transistors to fit cell height.")
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
|
|
@ -137,7 +139,7 @@ class pinv(pgate.pgate):
|
|||
# the well width is determined the multi-finger PMOS device width plus
|
||||
# the well contact width and half well enclosure on both sides
|
||||
self.well_width = self.pmos.active_width + self.pmos.active_contact.width \
|
||||
+ drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
|
||||
+ drc("active_to_body_active") + 2*drc("well_enclosure_active")
|
||||
self.width = self.well_width
|
||||
# Height is an input parameter, so it is not recomputed.
|
||||
|
||||
|
|
@ -281,3 +283,14 @@ class pinv(pgate.pgate):
|
|||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||
transition_prob = spice["inv_transition_prob"]
|
||||
return transition_prob*(c_load + c_para)
|
||||
|
||||
def get_cin(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_effort_stage(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.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
|
|
|
|||
|
|
@ -94,16 +94,16 @@ class pinvbuf(design.design):
|
|||
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
|
||||
|
||||
def place_modules(self):
|
||||
# Add INV1 to the right (capacitance shield)
|
||||
# Add INV1 to the left (capacitance shield)
|
||||
self.inv1_inst.place(vector(0,0))
|
||||
|
||||
# Add INV2 to the right
|
||||
# Add INV2 to the right of INV1
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||
|
||||
# Add INV3 to the right
|
||||
# Add INV3 to the right of INV2
|
||||
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
|
||||
|
||||
# Add INV4 to the bottom
|
||||
# Add INV4 flipped to the bottom aligned with INV2
|
||||
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
|
||||
mirror = "MX")
|
||||
|
||||
|
|
@ -187,3 +187,34 @@ class pinvbuf(design.design):
|
|||
inv2_delay = self.inv2.analytical_delay(slew=inv1_delay.slew, load=load)
|
||||
return inv1_delay + inv2_delay
|
||||
|
||||
def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||
"""Get the stage efforts of the clk -> clk_buf path"""
|
||||
stage_effort_list = []
|
||||
stage1_cout = self.inv1.get_cin() + self.inv2.get_cin()
|
||||
stage1 = self.inv.get_effort_stage(stage1_cout, inp_is_rise)
|
||||
stage_effort_list.append(stage1)
|
||||
last_stage_is_rise = stage1.is_rise
|
||||
|
||||
stage2 = self.inv2.get_effort_stage(external_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage2)
|
||||
|
||||
return stage_effort_list
|
||||
|
||||
def determine_clk_buf_bar_stage_efforts(self, external_cout, inp_is_rise=False):
|
||||
"""Get the stage efforts of the clk -> clk_buf path"""
|
||||
#After (almost) every stage, the direction of the signal inverts.
|
||||
stage_effort_list = []
|
||||
stage1_cout = self.inv1.get_cin() + self.inv2.get_cin()
|
||||
stage1 = self.inv.get_effort_stage(stage1_cout, inp_is_rise)
|
||||
stage_effort_list.append(stage1)
|
||||
last_stage_is_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
stage2_cout = self.inv2.get_cin()
|
||||
stage2 = self.inv1.get_effort_stage(stage2_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage2)
|
||||
last_stage_is_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
stage3 = self.inv2.get_effort_stage(external_cout, last_stage_is_rise)
|
||||
stage_effort_list.append(stage3)
|
||||
|
||||
return stage_effort_list
|
||||
|
|
@ -5,6 +5,7 @@ from tech import drc, parameter, spice
|
|||
from ptx import ptx
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
class pnand2(pgate.pgate):
|
||||
"""
|
||||
|
|
@ -21,10 +22,11 @@ class pnand2(pgate.pgate):
|
|||
pgate.pgate.__init__(self, name, height)
|
||||
debug.info(2, "create pnand2 structure {0} with size of {1}".format(name, size))
|
||||
|
||||
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"]
|
||||
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||
|
||||
# FIXME: Allow these to be sized
|
||||
debug.check(size==1,"Size 1 pnand2 is only supported now.")
|
||||
|
|
@ -33,7 +35,6 @@ class pnand2(pgate.pgate):
|
|||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
#self.DRC_LVS()
|
||||
|
||||
|
||||
def create_netlist(self):
|
||||
|
|
@ -91,7 +92,7 @@ class pnand2(pgate.pgate):
|
|||
# Two PMOS devices and a well contact. Separation between each.
|
||||
# Enclosure space on the sides.
|
||||
self.well_width = 2*self.pmos.active_width + contact.active.width \
|
||||
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
|
||||
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
|
||||
|
||||
self.width = self.well_width
|
||||
# Height is an input parameter, so it is not recomputed.
|
||||
|
|
@ -100,7 +101,7 @@ class pnand2(pgate.pgate):
|
|||
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
||||
# This is a poly-to-poly of a flipped cell
|
||||
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
|
||||
drc["poly_extend_active"], self.poly_space)
|
||||
drc("poly_extend_active"), self.poly_space)
|
||||
|
||||
def route_supply_rails(self):
|
||||
""" Add vdd/gnd rails to the top and bottom. """
|
||||
|
|
@ -192,26 +193,33 @@ class pnand2(pgate.pgate):
|
|||
""" Route the Z output """
|
||||
# PMOS1 drain
|
||||
pmos_pin = self.pmos1_inst.get_pin("D")
|
||||
top_pin_offset = pmos_pin.center()
|
||||
# NMOS2 drain
|
||||
nmos_pin = self.nmos2_inst.get_pin("D")
|
||||
nmos_pin = self.nmos2_inst.get_pin("D")
|
||||
bottom_pin_offset = nmos_pin.center()
|
||||
|
||||
# Output pin
|
||||
mid_offset = vector(nmos_pin.center().x,self.inputA_yoffset)
|
||||
out_offset = vector(nmos_pin.center().x + self.m1_pitch,self.inputA_yoffset)
|
||||
|
||||
# Midpoints of the L routes go horizontal first then vertical
|
||||
mid1_offset = vector(out_offset.x, top_pin_offset.y)
|
||||
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
|
||||
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=pmos_pin.center())
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=nmos_pin.center())
|
||||
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=mid_offset,
|
||||
offset=out_offset,
|
||||
rotate=90)
|
||||
|
||||
# PMOS1 to mid-drain to NMOS2 drain
|
||||
self.add_path("metal2",[pmos_pin.bc(), mid_offset, nmos_pin.uc()])
|
||||
self.add_path("metal2",[top_pin_offset, mid1_offset, out_offset, mid2_offset, bottom_pin_offset])
|
||||
|
||||
# This extends the output to the edge of the cell
|
||||
self.add_layout_pin_rect_center(text="Z",
|
||||
layer="metal1",
|
||||
offset=mid_offset,
|
||||
offset=out_offset,
|
||||
width=contact.m1m2.first_layer_height,
|
||||
height=contact.m1m2.first_layer_width)
|
||||
|
||||
|
|
@ -242,3 +250,14 @@ class pnand2(pgate.pgate):
|
|||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||
transition_prob = spice["nand2_transition_prob"]
|
||||
return transition_prob*(c_load + c_para)
|
||||
|
||||
def get_cin(self):
|
||||
"""Return the relative input capacitance of a single input"""
|
||||
return self.nmos_size+self.pmos_size
|
||||
|
||||
def get_effort_stage(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.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
|
|
@ -23,10 +23,11 @@ class pnand3(pgate.pgate):
|
|||
|
||||
# We have trouble pitch matching a 3x sizes to the bitcell...
|
||||
# If we relax this, we could size this better.
|
||||
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"]
|
||||
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||
|
||||
# FIXME: Allow these to be sized
|
||||
debug.check(size==1,"Size 1 pnand3 is only supported now.")
|
||||
|
|
@ -83,7 +84,7 @@ class pnand3(pgate.pgate):
|
|||
# Two PMOS devices and a well contact. Separation between each.
|
||||
# Enclosure space on the sides.
|
||||
self.well_width = 3*self.pmos.active_width + self.pmos.active_contact.width \
|
||||
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"] \
|
||||
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active") \
|
||||
- self.overlap_offset.x
|
||||
self.width = self.well_width
|
||||
# Height is an input parameter, so it is not recomputed.
|
||||
|
|
@ -96,7 +97,7 @@ class pnand3(pgate.pgate):
|
|||
extra_contact_space = max(-nmos.get_pin("D").by(),0)
|
||||
# This is a poly-to-poly of a flipped cell
|
||||
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
|
||||
drc["poly_extend_active"], self.poly_space)
|
||||
drc("poly_extend_active"), self.poly_space)
|
||||
|
||||
def route_supply_rails(self):
|
||||
""" Add vdd/gnd rails to the top and bottom. """
|
||||
|
|
@ -191,7 +192,7 @@ class pnand3(pgate.pgate):
|
|||
metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width,
|
||||
self.m1_space + 0.5*contact.poly.width + 0.5*self.m1_width)
|
||||
|
||||
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc["poly_to_active"])
|
||||
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc("poly_to_active"))
|
||||
inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing
|
||||
self.route_input_gate(self.pmos3_inst, self.nmos3_inst, inputC_yoffset, "C", position="center")
|
||||
|
||||
|
|
@ -261,3 +262,14 @@ class pnand3(pgate.pgate):
|
|||
c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff
|
||||
transition_prob = spice["nand3_transition_prob"]
|
||||
return transition_prob*(c_load + c_para)
|
||||
|
||||
def get_cin(self):
|
||||
"""Return the relative input capacitance of a single input"""
|
||||
return self.nmos_size+self.pmos_size
|
||||
|
||||
def get_effort_stage(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 = 3
|
||||
return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
|
|
@ -24,8 +24,8 @@ class pnor2(pgate.pgate):
|
|||
self.nmos_size = size
|
||||
# We will just make this 1.5 times for now. NORs are not ideal anyhow.
|
||||
self.pmos_size = 1.5*parameter["beta"]*size
|
||||
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
|
||||
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
|
||||
self.nmos_width = self.nmos_size*drc("minwidth_tx")
|
||||
self.pmos_width = self.pmos_size*drc("minwidth_tx")
|
||||
|
||||
# FIXME: Allow these to be sized
|
||||
debug.check(size==1,"Size 1 pnor2 is only supported now.")
|
||||
|
|
@ -92,7 +92,7 @@ class pnor2(pgate.pgate):
|
|||
# Two PMOS devices and a well contact. Separation between each.
|
||||
# Enclosure space on the sides.
|
||||
self.well_width = 2*self.pmos.active_width + self.pmos.active_contact.width \
|
||||
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
|
||||
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
|
||||
|
||||
self.width = self.well_width
|
||||
# Height is an input parameter, so it is not recomputed.
|
||||
|
|
@ -101,7 +101,7 @@ class pnor2(pgate.pgate):
|
|||
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
|
||||
# This is a poly-to-poly of a flipped cell
|
||||
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
|
||||
drc["poly_extend_active"], self.poly_space)
|
||||
drc("poly_extend_active"), self.poly_space)
|
||||
|
||||
def add_supply_rails(self):
|
||||
""" Add vdd/gnd rails to the top and bottom. """
|
||||
|
|
|
|||
|
|
@ -51,10 +51,12 @@ class precharge(pgate.pgate):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
self.add_pin_list(["bl", "br", "en", "vdd"])
|
||||
self.add_pin_list(["bl", "br", "en_bar", "vdd"])
|
||||
|
||||
def add_ptx(self):
|
||||
"""Initializes the upper and lower pmos"""
|
||||
"""
|
||||
Initializes the upper and lower pmos
|
||||
"""
|
||||
self.pmos = ptx(width=self.ptx_width,
|
||||
tx_type="pmos")
|
||||
self.add_mod(self.pmos)
|
||||
|
|
@ -63,56 +65,58 @@ class precharge(pgate.pgate):
|
|||
|
||||
|
||||
def route_vdd_rail(self):
|
||||
"""
|
||||
Adds a vdd rail at the top of the cell
|
||||
"""
|
||||
|
||||
"""Adds a vdd rail at the top of the cell"""
|
||||
# adds the rail across the width of the cell
|
||||
vdd_position = vector(0, self.height - self.m1_width)
|
||||
self.add_rect(layer="metal1",
|
||||
offset=vdd_position,
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
# Adds the rail across the width of the cell
|
||||
vdd_position = vector(0.5*self.width, self.height)
|
||||
self.add_rect_center(layer="metal1",
|
||||
offset=vdd_position,
|
||||
width=self.width,
|
||||
height=self.m1_width)
|
||||
|
||||
pmos_pin = self.upper_pmos2_inst.get_pin("S")
|
||||
# center of vdd rail
|
||||
vdd_pos = vector(pmos_pin.cx(), vdd_position.y + 0.5*self.m1_width)
|
||||
self.add_path("metal1", [pmos_pin.uc(), vdd_pos])
|
||||
pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y)
|
||||
self.add_path("metal1", [pmos_pin.uc(), pmos_vdd_pos])
|
||||
|
||||
# Add the M1->M2->M3 stack at the left edge
|
||||
self.add_via_center(layers=("metal1", "via1", "metal2"),
|
||||
offset=vdd_pos.scale(0,1))
|
||||
self.add_via_center(layers=("metal2", "via2", "metal3"),
|
||||
offset=vdd_pos.scale(0,1))
|
||||
self.add_layout_pin_rect_center(text="vdd",
|
||||
layer="metal3",
|
||||
offset=vdd_pos.scale(0,1))
|
||||
# Add vdd pin above the transistor
|
||||
self.add_power_pin("vdd", pmos_pin.center(), rotate=0)
|
||||
|
||||
|
||||
def create_ptx(self):
|
||||
"""Create both the upper_pmos and lower_pmos to the module"""
|
||||
"""
|
||||
Create both the upper_pmos and lower_pmos to the module
|
||||
"""
|
||||
|
||||
self.lower_pmos_inst=self.add_inst(name="lower_pmos",
|
||||
mod=self.pmos)
|
||||
self.connect_inst(["bl", "en", "br", "vdd"])
|
||||
self.connect_inst(["bl", "en_bar", "br", "vdd"])
|
||||
|
||||
self.upper_pmos1_inst=self.add_inst(name="upper_pmos1",
|
||||
mod=self.pmos)
|
||||
self.connect_inst(["bl", "en", "vdd", "vdd"])
|
||||
self.connect_inst(["bl", "en_bar", "vdd", "vdd"])
|
||||
|
||||
self.upper_pmos2_inst=self.add_inst(name="upper_pmos2",
|
||||
mod=self.pmos)
|
||||
self.connect_inst(["br", "en", "vdd", "vdd"])
|
||||
self.connect_inst(["br", "en_bar", "vdd", "vdd"])
|
||||
|
||||
|
||||
def place_ptx(self):
|
||||
"""Place both the upper_pmos and lower_pmos to the module"""
|
||||
"""
|
||||
Place both the upper_pmos and lower_pmos to the module
|
||||
"""
|
||||
|
||||
# Compute the other pmos2 location, but determining offset to overlap the
|
||||
# source and drain pins
|
||||
self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
|
||||
overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll()
|
||||
# This is how much the contact is placed inside the ptx active
|
||||
contact_xdiff = self.pmos.get_pin("S").lx()
|
||||
|
||||
# adds the lower pmos to layout
|
||||
#base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0)
|
||||
self.lower_pmos_position = vector(self.bitcell.get_pin(self.bitcell_bl).lx(),
|
||||
bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx()
|
||||
self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, self.well_enclose_active),
|
||||
self.pmos.active_offset.y)
|
||||
self.lower_pmos_inst.place(self.lower_pmos_position)
|
||||
|
||||
|
|
@ -121,11 +125,13 @@ class precharge(pgate.pgate):
|
|||
self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff)
|
||||
self.upper_pmos1_inst.place(self.upper_pmos1_pos)
|
||||
|
||||
upper_pmos2_pos = self.upper_pmos1_pos + self.overlap_offset
|
||||
upper_pmos2_pos = self.upper_pmos1_pos + overlap_offset
|
||||
self.upper_pmos2_inst.place(upper_pmos2_pos)
|
||||
|
||||
def connect_poly(self):
|
||||
"""Connects the upper and lower pmos together"""
|
||||
"""
|
||||
Connects the upper and lower pmos together
|
||||
"""
|
||||
|
||||
offset = self.lower_pmos_inst.get_pin("G").ll()
|
||||
# connects the top and bottom pmos' gates together
|
||||
|
|
@ -144,7 +150,10 @@ class precharge(pgate.pgate):
|
|||
height=self.poly_width)
|
||||
|
||||
def route_en(self):
|
||||
"""Adds the en input rail, en contact/vias, and connects to the pmos"""
|
||||
"""
|
||||
Adds the en input rail, en contact/vias, and connects to the pmos
|
||||
"""
|
||||
|
||||
# adds the en contact to connect the gates to the en rail on metal1
|
||||
offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space)
|
||||
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
||||
|
|
@ -152,25 +161,29 @@ class precharge(pgate.pgate):
|
|||
rotate=90)
|
||||
|
||||
# adds the en rail on metal1
|
||||
self.add_layout_pin_segment_center(text="en",
|
||||
self.add_layout_pin_segment_center(text="en_bar",
|
||||
layer="metal1",
|
||||
start=offset.scale(0,1),
|
||||
end=offset.scale(0,1)+vector(self.width,0))
|
||||
|
||||
|
||||
def place_nwell_and_contact(self):
|
||||
"""Adds a nwell tap to connect to the vdd rail"""
|
||||
"""
|
||||
Adds a nwell tap to connect to the vdd rail
|
||||
"""
|
||||
|
||||
# adds the contact from active to metal1
|
||||
well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1,0) \
|
||||
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc["well_extend_active"])
|
||||
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc("well_extend_active"))
|
||||
self.add_contact_center(layers=("active", "contact", "metal1"),
|
||||
offset=well_contact_pos,
|
||||
implant_type="n",
|
||||
well_type="n")
|
||||
|
||||
# leave an extra pitch for the height
|
||||
self.height = well_contact_pos.y + contact.well.height + self.m1_pitch
|
||||
|
||||
self.height = well_contact_pos.y + contact.well.height
|
||||
|
||||
# nwell should span the whole design since it is pmos only
|
||||
self.add_rect(layer="nwell",
|
||||
offset=vector(0,0),
|
||||
width=self.width,
|
||||
|
|
@ -178,58 +191,81 @@ class precharge(pgate.pgate):
|
|||
|
||||
|
||||
def route_bitlines(self):
|
||||
"""Adds both bit-line and bit-line-bar to the module"""
|
||||
"""
|
||||
Adds both bit-line and bit-line-bar to the module
|
||||
"""
|
||||
|
||||
# adds the BL on metal 2
|
||||
offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(),0) - vector(0.5 * self.m2_width,0)
|
||||
self.add_layout_pin(text="bl",
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
width=drc['minwidth_metal2'],
|
||||
height=self.height)
|
||||
self.bl_pin = self.add_layout_pin(text="bl",
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
width=drc("minwidth_metal2"),
|
||||
height=self.height)
|
||||
|
||||
# adds the BR on metal 2
|
||||
offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(),0) - vector(0.5 * self.m2_width,0)
|
||||
self.add_layout_pin(text="br",
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
width=drc['minwidth_metal2'],
|
||||
height=self.height)
|
||||
self.br_pin = self.add_layout_pin(text="br",
|
||||
layer="metal2",
|
||||
offset=offset,
|
||||
width=drc("minwidth_metal2"),
|
||||
height=self.height)
|
||||
|
||||
def connect_to_bitlines(self):
|
||||
"""
|
||||
Connect the bitlines to the devices
|
||||
"""
|
||||
self.add_bitline_contacts()
|
||||
self.connect_pmos(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
|
||||
self.connect_pmos(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
|
||||
self.connect_pmos(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
|
||||
self.connect_pmos(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
|
||||
self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl"))
|
||||
self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl"))
|
||||
self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"),self.get_pin("br"))
|
||||
self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br"))
|
||||
|
||||
|
||||
def add_bitline_contacts(self):
|
||||
"""Adds contacts/via from metal1 to metal2 for bit-lines"""
|
||||
"""
|
||||
Adds contacts/via from metal1 to metal2 for bit-lines
|
||||
"""
|
||||
|
||||
stack=("metal1", "via1", "metal2")
|
||||
pos = self.lower_pmos_inst.get_pin("S").center()
|
||||
self.add_contact_center(layers=stack,
|
||||
offset=pos)
|
||||
pos = self.lower_pmos_inst.get_pin("D").center()
|
||||
self.add_contact_center(layers=stack,
|
||||
offset=pos)
|
||||
pos = self.upper_pmos1_inst.get_pin("S").center()
|
||||
self.add_contact_center(layers=stack,
|
||||
offset=pos)
|
||||
pos = self.upper_pmos2_inst.get_pin("D").center()
|
||||
self.add_contact_center(layers=stack,
|
||||
offset=pos)
|
||||
|
||||
def connect_pmos(self, pmos_pin, bit_pin):
|
||||
""" Connect pmos pin to bitline pin """
|
||||
|
||||
ll_pos = vector(min(pmos_pin.lx(),bit_pin.lx()), pmos_pin.by())
|
||||
ur_pos = vector(max(pmos_pin.rx(),bit_pin.rx()), pmos_pin.uy())
|
||||
|
||||
width = ur_pos.x-ll_pos.x
|
||||
height = ur_pos.y-ll_pos.y
|
||||
self.add_rect(layer="metal2",
|
||||
offset=ll_pos,
|
||||
width=width,
|
||||
height=height)
|
||||
upper_pin = self.upper_pmos1_inst.get_pin("S")
|
||||
lower_pin = self.lower_pmos_inst.get_pin("S")
|
||||
|
||||
# BL goes up to M2 at the transistor
|
||||
self.bl_contact=self.add_contact_center(layers=stack,
|
||||
offset=upper_pin.center())
|
||||
self.add_contact_center(layers=stack,
|
||||
offset=lower_pin.center())
|
||||
|
||||
# BR routes over on M1 first
|
||||
self.add_contact_center(layers=stack,
|
||||
offset = vector(self.br_pin.cx(), upper_pin.cy()))
|
||||
self.add_contact_center(layers=stack,
|
||||
offset = vector(self.br_pin.cx(), lower_pin.cy()))
|
||||
|
||||
def connect_pmos_m1(self, pmos_pin, bit_pin):
|
||||
"""
|
||||
Connect a pmos pin to bitline pin
|
||||
"""
|
||||
|
||||
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||
|
||||
self.add_path("metal1", [ left_pos, right_pos] )
|
||||
|
||||
def connect_pmos_m2(self, pmos_pin, bit_pin):
|
||||
"""
|
||||
Connect a pmos pin to bitline pin
|
||||
"""
|
||||
|
||||
left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||
right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy())
|
||||
|
||||
self.add_path("metal2", [ left_pos, right_pos], self.bl_contact.height)
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the relative capacitance of the enable in the precharge cell"""
|
||||
#The enable connect to three pmos gates. They all use the same size pmos.
|
||||
pmos_cin = self.pmos.get_cin()
|
||||
return 3*pmos_cin
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import design
|
||||
import debug
|
||||
from tech import drc, info, spice
|
||||
from tech import drc, spice
|
||||
from vector import vector
|
||||
from contact import contact
|
||||
from globals import OPTS
|
||||
|
|
@ -15,7 +15,7 @@ class ptx(design.design):
|
|||
you to connect the fingered gates and active for parallel devices.
|
||||
|
||||
"""
|
||||
def __init__(self, width=drc["minwidth_tx"], mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
|
||||
def __init__(self, width=drc("minwidth_tx"), mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
|
||||
# We need to keep unique names because outputting to GDSII
|
||||
# will use the last record with a given name. I.e., you will
|
||||
# over-write a design in GDS if one has and the other doesn't
|
||||
|
|
@ -41,8 +41,9 @@ class ptx(design.design):
|
|||
self.num_contacts = num_contacts
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
# We must always create ptx layout for pbitcell
|
||||
# some transistor sizes in other netlist depend on pbitcell
|
||||
self.create_layout()
|
||||
|
||||
|
||||
|
||||
|
|
@ -65,14 +66,14 @@ class ptx(design.design):
|
|||
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
|
||||
# " ".join(self.pins)))
|
||||
# Just make a guess since these will actually be decided in the layout later.
|
||||
area_sd = 2.5*drc["minwidth_poly"]*self.tx_width
|
||||
perimeter_sd = 2*drc["minwidth_poly"] + 2*self.tx_width
|
||||
self.spice_device="M{{0}} {{1}} {0} m={1} w={2}u l={3}u pd={4}u ps={4}u as={5}p ad={5}p".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc["minwidth_poly"],
|
||||
perimeter_sd,
|
||||
area_sd)
|
||||
area_sd = 2.5*drc("minwidth_poly")*self.tx_width
|
||||
perimeter_sd = 2*drc("minwidth_poly") + 2*self.tx_width
|
||||
self.spice_device="M{{0}} {{1}} {0} m={1} w={2}u l={3}u pd={4:.2f}u ps={4:.2f}u as={5:.2f}p ad={5:.2f}p".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"),
|
||||
perimeter_sd,
|
||||
area_sd)
|
||||
self.spice.append("\n* ptx " + self.spice_device)
|
||||
# self.spice.append(".ENDS {0}".format(self.name))
|
||||
|
||||
|
|
@ -108,7 +109,7 @@ class ptx(design.design):
|
|||
self.contact_pitch = 2*self.contact_to_gate + self.contact_width + self.poly_width
|
||||
|
||||
# The enclosure of an active contact. Not sure about second term.
|
||||
active_enclose_contact = max(drc["active_enclosure_contact"],
|
||||
active_enclose_contact = max(drc("active_enclosure_contact"),
|
||||
(self.active_width - self.contact_width)/2)
|
||||
# This is the distance from the edge of poly to the contacted end of active
|
||||
self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate
|
||||
|
|
@ -128,7 +129,7 @@ class ptx(design.design):
|
|||
self.active_offset = vector([self.well_enclose_active]*2)
|
||||
|
||||
# Well enclosure of active, ensure minwidth as well
|
||||
if info["has_{}well".format(self.well_type)]:
|
||||
if drc("has_{}well".format(self.well_type)):
|
||||
self.cell_well_width = max(self.active_width + 2*self.well_enclose_active,
|
||||
self.well_width)
|
||||
self.cell_well_height = max(self.tx_width + 2*self.well_enclose_active,
|
||||
|
|
@ -150,9 +151,9 @@ class ptx(design.design):
|
|||
|
||||
|
||||
# Min area results are just flagged for now.
|
||||
debug.check(self.active_width*self.active_height>=drc["minarea_active"],"Minimum active area violated.")
|
||||
debug.check(self.active_width*self.active_height>=drc("minarea_active"),"Minimum active area violated.")
|
||||
# We do not want to increase the poly dimensions to fix an area problem as it would cause an LVS issue.
|
||||
debug.check(self.poly_width*self.poly_height>=drc["minarea_poly"],"Minimum poly area violated.")
|
||||
debug.check(self.poly_width*self.poly_height>=drc("minarea_poly"),"Minimum poly area violated.")
|
||||
|
||||
def connect_fingered_poly(self, poly_positions):
|
||||
"""
|
||||
|
|
@ -180,7 +181,7 @@ class ptx(design.design):
|
|||
layer="poly",
|
||||
offset=poly_offset,
|
||||
width=poly_width,
|
||||
height=drc["minwidth_poly"])
|
||||
height=drc("minwidth_poly"))
|
||||
|
||||
|
||||
def connect_fingered_active(self, drain_positions, source_positions):
|
||||
|
|
@ -268,7 +269,7 @@ class ptx(design.design):
|
|||
height=self.active_height)
|
||||
# If the implant must enclose the active, shift offset
|
||||
# and increase width/height
|
||||
enclose_width = drc["implant_enclosure_active"]
|
||||
enclose_width = drc("implant_enclosure_active")
|
||||
enclose_offset = [enclose_width]*2
|
||||
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||
offset=self.active_offset - enclose_offset,
|
||||
|
|
@ -279,7 +280,7 @@ class ptx(design.design):
|
|||
"""
|
||||
Add an (optional) well and implant for the type of transistor.
|
||||
"""
|
||||
if info["has_{}well".format(self.well_type)]:
|
||||
if drc("has_{}well".format(self.well_type)):
|
||||
self.add_rect(layer="{}well".format(self.well_type),
|
||||
offset=(0,0),
|
||||
width=self.cell_well_width,
|
||||
|
|
@ -352,4 +353,6 @@ class ptx(design.design):
|
|||
if self.connect_active:
|
||||
self.connect_fingered_active(drain_positions, source_positions)
|
||||
|
||||
|
||||
def get_cin(self):
|
||||
"""Returns the relative gate cin of the tx"""
|
||||
return self.tx_width/drc("minwidth_tx")
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import design
|
||||
import debug
|
||||
from tech import drc, info
|
||||
from tech import drc
|
||||
from vector import vector
|
||||
import contact
|
||||
from ptx import ptx
|
||||
|
|
@ -9,18 +9,21 @@ from globals import OPTS
|
|||
class single_level_column_mux(design.design):
|
||||
"""
|
||||
This module implements the columnmux bitline cell used in the design.
|
||||
Creates a single columnmux cell.
|
||||
Creates a single columnmux cell with the given integer size relative
|
||||
to minimum size. Default is 8x. Per Samira and Hodges-Jackson book:
|
||||
Column-mux transistors driven by the decoder must be sized for optimal speed
|
||||
"""
|
||||
|
||||
# This is needed for different bitline spacings
|
||||
unique_id = 1
|
||||
|
||||
def __init__(self, tx_size, bitcell_bl="bl", bitcell_br="br"):
|
||||
name="single_level_column_mux_{}_no{}".format(tx_size,single_level_column_mux.unique_id)
|
||||
def __init__(self, tx_size=8, bitcell_bl="bl", bitcell_br="br"):
|
||||
self.tx_size = int(tx_size)
|
||||
name="single_level_column_mux_{}_{}".format(self.tx_size,single_level_column_mux.unique_id)
|
||||
single_level_column_mux.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
debug.info(2, "create single column mux cell: {0}".format(name))
|
||||
|
||||
self.tx_size = tx_size
|
||||
self.bitcell_bl = bitcell_bl
|
||||
self.bitcell_br = bitcell_br
|
||||
|
||||
|
|
@ -52,7 +55,7 @@ class single_level_column_mux(design.design):
|
|||
self.bitcell = self.mod_bitcell()
|
||||
|
||||
# Adds nmos_lower,nmos_upper to the module
|
||||
self.ptx_width = self.tx_size * drc["minwidth_tx"]
|
||||
self.ptx_width = self.tx_size*drc("minwidth_tx")
|
||||
self.nmos = ptx(width=self.ptx_width)
|
||||
self.add_mod(self.nmos)
|
||||
|
||||
|
|
@ -144,8 +147,8 @@ class single_level_column_mux(design.design):
|
|||
# bl_out -> nmos_upper/S on metal2
|
||||
self.add_path("metal1",[bl_pin.ll(), vector(nmos_upper_d_pin.cx(),bl_pin.by()), nmos_upper_d_pin.center()])
|
||||
# halfway up, move over
|
||||
mid1 = bl_out_pin.uc().scale(1,0.5)+nmos_upper_s_pin.bc().scale(0,0.5)
|
||||
mid2 = bl_out_pin.uc().scale(0,0.5)+nmos_upper_s_pin.bc().scale(1,0.5)
|
||||
mid1 = bl_out_pin.uc().scale(1,0.4)+nmos_upper_s_pin.bc().scale(0,0.4)
|
||||
mid2 = bl_out_pin.uc().scale(0,0.4)+nmos_upper_s_pin.bc().scale(1,0.4)
|
||||
self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()])
|
||||
|
||||
# br -> nmos_lower/D on metal2
|
||||
|
|
@ -164,7 +167,7 @@ class single_level_column_mux(design.design):
|
|||
"""
|
||||
|
||||
# Add it to the right, aligned in between the two tx
|
||||
active_pos = vector(self.bitcell.width,self.nmos_upper.by())
|
||||
active_pos = vector(self.bitcell.width,self.nmos_upper.by() - 0.5*self.poly_space)
|
||||
active_via = self.add_via_center(layers=("active", "contact", "metal1"),
|
||||
offset=active_pos,
|
||||
implant_type="p",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import pstats
|
||||
p = pstats.Stats('profile.dat')
|
||||
p.strip_dirs()
|
||||
p.sort_stats('cumulative')
|
||||
p.print_stats(50)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
from enum import Enum
|
||||
from vector3d import vector3d
|
||||
|
||||
class direction(Enum):
|
||||
NORTH = 1
|
||||
SOUTH = 2
|
||||
EAST = 3
|
||||
WEST = 4
|
||||
UP = 5
|
||||
DOWN = 6
|
||||
NORTHEAST = 7
|
||||
NORTHWEST = 8
|
||||
SOUTHEAST = 9
|
||||
SOUTHWEST = 10
|
||||
|
||||
|
||||
def get_offset(direct):
|
||||
"""
|
||||
Returns the vector offset for a given direction.
|
||||
"""
|
||||
if direct==direction.NORTH:
|
||||
offset = vector3d(0,1,0)
|
||||
elif direct==direction.SOUTH:
|
||||
offset = vector3d(0,-1,0)
|
||||
elif direct==direction.EAST:
|
||||
offset = vector3d(1,0,0)
|
||||
elif direct==direction.WEST:
|
||||
offset = vector3d(-1,0,0)
|
||||
elif direct==direction.UP:
|
||||
offset = vector3d(0,0,1)
|
||||
elif direct==direction.DOWN:
|
||||
offset = vector3d(0,0,-1)
|
||||
elif direct==direction.NORTHEAST:
|
||||
offset = vector3d(1,1,0)
|
||||
elif direct==direction.NORTHWEST:
|
||||
offset = vector3d(-1,1,0)
|
||||
elif direct==direction.SOUTHEAST:
|
||||
offset = vector3d(1,-1,0)
|
||||
elif direct==direction.SOUTHWEST:
|
||||
offset = vector3d(-1,-1,0)
|
||||
else:
|
||||
debug.error("Invalid direction {}".format(direct))
|
||||
|
||||
return offset
|
||||
|
||||
def cardinal_directions(up_down_too=False):
|
||||
temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
|
||||
if up_down_too:
|
||||
temp_dirs.extend([direction.UP, direction.DOWN])
|
||||
return temp_dirs
|
||||
|
||||
def cardinal_offsets(up_down_too=False):
|
||||
return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)]
|
||||
|
||||
def all_directions():
|
||||
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST,
|
||||
direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST]
|
||||
|
||||
def all_offsets():
|
||||
return [direction.get_offset(d) for d in direction.all_directions()]
|
||||
|
||||
def all_neighbors(cell):
|
||||
return [cell+x for x in direction.all_offsets()]
|
||||
|
||||
def cardinal_neighbors(cell):
|
||||
return [cell+x for x in direction.cardinal_offsets()]
|
||||
|
||||
|
|
@ -1,95 +1,138 @@
|
|||
import numpy as np
|
||||
import string
|
||||
from itertools import tee
|
||||
import debug
|
||||
from vector3d import vector3d
|
||||
from cell import cell
|
||||
import os
|
||||
from grid_cell import grid_cell
|
||||
|
||||
class grid:
|
||||
"""
|
||||
A two layer routing map. Each cell can be blocked in the vertical
|
||||
or horizontal layer.
|
||||
"""
|
||||
# costs are relative to a unit grid
|
||||
# non-preferred cost allows an off-direction jog of 1 grid
|
||||
# rather than 2 vias + preferred direction (cost 5)
|
||||
VIA_COST = 2
|
||||
NONPREFERRED_COST = 4
|
||||
PREFERRED_COST = 1
|
||||
|
||||
def __init__(self):
|
||||
|
||||
def __init__(self, ll, ur, track_width):
|
||||
""" Initialize the map and define the costs. """
|
||||
|
||||
# costs are relative to a unit grid
|
||||
# non-preferred cost allows an off-direction jog of 1 grid
|
||||
# rather than 2 vias + preferred direction (cost 5)
|
||||
self.VIA_COST = 2
|
||||
self.NONPREFERRED_COST = 4
|
||||
self.PREFERRED_COST = 1
|
||||
# list of the source/target grid coordinates
|
||||
self.source = []
|
||||
self.target = []
|
||||
|
||||
self.track_width = track_width
|
||||
self.track_widths = [self.track_width, self.track_width, 1.0]
|
||||
self.track_factor = [1/self.track_width, 1/self.track_width, 1.0]
|
||||
|
||||
# The bounds are in grids for this
|
||||
# This is really lower left bottom layer and upper right top layer in 3D.
|
||||
self.ll = vector3d(ll.x,ll.y,0).scale(self.track_factor).round()
|
||||
self.ur = vector3d(ur.x,ur.y,1).scale(self.track_factor).round()
|
||||
|
||||
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||
self.map={}
|
||||
|
||||
def set_blocked(self,n):
|
||||
self.add_map(n)
|
||||
self.map[n].blocked=True
|
||||
def add_all_grids(self):
|
||||
for x in range(self.ll.x, self.ur.x, 1):
|
||||
for y in range(self.ll.y, self.ur.y, 1):
|
||||
self.add_map(vector3d(x,y,0))
|
||||
self.add_map(vector3d(x,y,1))
|
||||
|
||||
def set_blocked(self,n,value=True):
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
self.set_blocked(item,value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].blocked=value
|
||||
|
||||
def is_blocked(self,n):
|
||||
self.add_map(n)
|
||||
return self.map[n].blocked
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
if self.is_blocked(item):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
self.add_map(n)
|
||||
return self.map[n].blocked
|
||||
|
||||
def add_blockage_shape(self,ll,ur,z):
|
||||
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
|
||||
|
||||
block_list = []
|
||||
for x in range(int(ll[0]),int(ur[0])+1):
|
||||
for y in range(int(ll[1]),int(ur[1])+1):
|
||||
block_list.append(vector3d(x,y,z))
|
||||
def set_path(self,n,value=True):
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
self.set_path(item,value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].path=value
|
||||
|
||||
self.add_blockage(block_list)
|
||||
def clear_blockages(self):
|
||||
self.set_blocked(set(self.map.keys()),False)
|
||||
|
||||
def set_source(self,n,value=True):
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
self.set_source(item,value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].source=value
|
||||
self.source.append(n)
|
||||
|
||||
def set_target(self,n,value=True):
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
self.set_target(item,value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].target=value
|
||||
self.target.append(n)
|
||||
|
||||
def add_blockage(self,block_list):
|
||||
debug.info(2,"Adding blockage list={0}".format(str(block_list)))
|
||||
for n in block_list:
|
||||
self.set_blocked(n)
|
||||
|
||||
def add_source(self,track_list,value=True):
|
||||
debug.info(3,"Adding source list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
debug.info(4,"Adding source ={0}".format(str(n)))
|
||||
self.set_source(n,value)
|
||||
self.set_blocked(n,False)
|
||||
|
||||
def add_map(self,p):
|
||||
|
||||
def add_target(self,track_list,value=True):
|
||||
debug.info(3,"Adding target list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
debug.info(4,"Adding target ={0}".format(str(n)))
|
||||
self.set_target(n,value)
|
||||
self.set_blocked(n,False)
|
||||
|
||||
def is_target(self,point):
|
||||
"""
|
||||
Point is in the target set, so we are done.
|
||||
"""
|
||||
return point in self.target
|
||||
|
||||
def add_map(self,n):
|
||||
"""
|
||||
Add a point to the map if it doesn't exist.
|
||||
"""
|
||||
if p not in self.map.keys():
|
||||
self.map[p]=cell()
|
||||
if isinstance(n, (list,tuple,set,frozenset)):
|
||||
for item in n:
|
||||
self.add_map(item)
|
||||
else:
|
||||
if n not in self.map.keys():
|
||||
self.map[n]=grid_cell()
|
||||
|
||||
def add_path(self,path):
|
||||
|
||||
def block_path(self,path):
|
||||
"""
|
||||
Mark the path in the routing grid for visualization
|
||||
Mark the path in the routing grid as blocked.
|
||||
Also unsets the path flag.
|
||||
"""
|
||||
self.path=path
|
||||
for p in path:
|
||||
self.map[p].path=True
|
||||
|
||||
def cost(self,path):
|
||||
"""
|
||||
The cost of the path is the length plus a penalty for the number
|
||||
of vias. We assume that non-preferred direction is penalized.
|
||||
"""
|
||||
|
||||
# Ignore the source pin layer change, FIXME?
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
plist = pairwise(path)
|
||||
cost = 0
|
||||
for p0,p1 in plist:
|
||||
if p0.z != p1.z: # via
|
||||
cost += self.VIA_COST
|
||||
elif p0.x != p1.x: # horizontal
|
||||
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
|
||||
elif p0.y != p1.y: # vertical
|
||||
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
|
||||
else:
|
||||
debug.error("Non-changing direction!")
|
||||
|
||||
return cost
|
||||
path.set_path(False)
|
||||
path.set_blocked(True)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
class cell:
|
||||
class grid_cell:
|
||||
"""
|
||||
A single cell that can be occupied in a given layer, blocked,
|
||||
visited, etc.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.visited = False
|
||||
self.path = False
|
||||
self.blocked = False
|
||||
self.source = False
|
||||
|
|
@ -17,13 +16,17 @@ class cell:
|
|||
Reset the dynamic info about routing. The pins/blockages are not reset so
|
||||
that they can be reused.
|
||||
"""
|
||||
self.visited=False
|
||||
self.min_cost=-1
|
||||
self.min_path=None
|
||||
self.blocked=False
|
||||
self.source=False
|
||||
self.target=False
|
||||
|
||||
def get_cost(self):
|
||||
# We can display the cost of the frontier
|
||||
if self.min_cost > 0:
|
||||
return self.min_cost
|
||||
|
||||
|
||||
def get_type(self):
|
||||
if self.blocked:
|
||||
|
|
@ -38,8 +41,4 @@ class cell:
|
|||
if self.path:
|
||||
return "P"
|
||||
|
||||
# We can display the cost of the frontier
|
||||
if self.min_cost > 0:
|
||||
return self.min_cost
|
||||
|
||||
return None
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue