Merge branch 'dev' into char

This commit is contained in:
Bugra Onal 2023-07-10 13:55:50 -07:00
commit eddc9af45b
18 changed files with 330956 additions and 103 deletions

View File

@ -23,14 +23,14 @@ jobs:
run: |
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology"
#cd $OPENRAM_HOME/tests
#export PDK_ROOT="${{ github.workspace }}/pdk"
#make pdk
#make install
export PDK_ROOT="${{ github.workspace }}/pdk"
make pdk
make install
- name: Regress
run: |
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology"
export PDK_ROOT="${{ github.workspace }}/pdk"
export FREEPDK45="~/FreePDK45"
# KLAYOUT_PATH breaks klayout installation. Unset it for now...
unset KLAYOUT_PATH

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.DS_Store
.coverage*
*~
*.orig
*.rej

View File

@ -49,6 +49,9 @@ INSTALL_BASE_DIRS := gds_lib mag_lib sp_lib lvs_lib calibre_lvs_lib klayout_lvs_
INSTALL_BASE := $(OPENRAM_HOME)/../technology/sky130
INSTALL_DIRS := $(addprefix $(INSTALL_BASE)/,$(INSTALL_BASE_DIRS))
# If conda is installed, we will use Magic from there
CONDA_DIR := $(wildcard $(TOP_DIR)/miniconda)
check-pdk-root:
ifndef PDK_ROOT
$(error PDK_ROOT is undefined, please export it before running make)
@ -69,12 +72,23 @@ $(OPEN_PDKS_DIR): $(SKY130_PDKS_DIR)
$(SKY130_PDK): $(OPEN_PDKS_DIR) $(SKY130_PDKS_DIR)
@echo "Installing open_pdks..."
ifeq ($(CONDA_DIR),"")
@cd $(PDK_ROOT)/open_pdks && \
./configure --enable-sky130-pdk=$(PDK_ROOT)/skywater-pdk/libraries --with-sky130-local-path=$(PDK_ROOT) && \
cd sky130 && \
make veryclean && \
make && \
make SHARED_PDKS_PATH=$(PDK_ROOT) install
else
@source $(TOP_DIR)/miniconda/bin/activate && \
cd $(PDK_ROOT)/open_pdks && \
./configure --enable-sky130-pdk=$(PDK_ROOT)/skywater-pdk/libraries --with-sky130-local-path=$(PDK_ROOT) && \
cd sky130 && \
make veryclean && \
make && \
make SHARED_PDKS_PATH=$(PDK_ROOT) install && \
conda deactivate
endif
$(SRAM_LIB_DIR): check-pdk-root
@echo "Cloning SRAM library..."

View File

@ -6,19 +6,24 @@ If you want to support a new technology, you will need to create:
We provide two technology examples for [SCMOS] and [FreePDK45]. Each
specific technology (e.g., [FreePDK45]) should be a subdirectory
(e.g., $OPENRAM_TECH/freepdk45) and include certain folders and files:
* gds_lib folder with all the .gds (premade) library cells:
* dff.gds
* sense_amp.gds
* write_driver.gds
* cell_1rw.gds
* replica\_cell\_1rw.gds
* dummy\_cell\_1rw.gds
* sp_lib folder with all the .sp (premade) library netlists for the above cells.
* layers.map
* A valid tech Python module (tech directory with \_\_init\_\_.py and tech.py) with:
(e.g., `$OPENRAM_TECH/freepdk45`) and include certain folders and files:
* `gds_lib` folder with all the `.gds` (premade) library cells:
* `dff.gds`
* `sense_amp.gds`
* `write_driver.gds`
* `cell_1rw.gds`
* `replica\_cell\_1rw.gds`
* `dummy\_cell\_1rw.gds`
* `sp_lib` folder with all the `.sp` (premade) library netlists for the above cells.
* `layers.map`
* A valid tech Python module (tech directory with `__init__.py` and `tech.py`) with:
* References in tech.py to spice models
* DRC/LVS rules needed for dynamic cells and routing
* Layer information
* Spice and supply information
* etc.
[FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents
[SCMOS]: https://www.mosis.com/files/scmos/scmos.pdf

View File

@ -4,6 +4,7 @@
[![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/)
[![License: BSD 3-clause](https://raw.githubusercontent.com/VLSIDA/OpenRAM/stable/images/license_badge.svg)](./LICENSE)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/openram?color=brightgreen&label=PyPI)](https://pypi.org/project/openram/)
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://githubtocolab.com/sfmth/openram-playground/blob/main/OpenRAM.ipynb)
An open-source static random access memory (SRAM) compiler.
@ -57,7 +58,7 @@ OpenRAM is licensed under the [BSD 3-Clause License](./LICENSE).
+ [M. R. Guthaus, J. E. Stine, S. Ataei, B. Chen, B. Wu, M. Sarwar, "OpenRAM: An Open-Source Memory Compiler," Proceedings of the 35th International Conference on Computer-Aided Design (ICCAD), 2016.](https://escholarship.org/content/qt8x19c778/qt8x19c778_noSplash_b2b3fbbb57f1269f86d0de77865b0691.pdf)
+ [S. Ataei, J. Stine, M. Guthaus, “A 64 kb differential single-port 12T SRAM design with a bit-interleaving scheme for low-voltage operation in 32 nm SOI CMOS,” International Conference on Computer Design (ICCD), 2016, pp. 499-506.](https://escholarship.org/uc/item/99f6q9c9)
+ [E. Ebrahimi, M. Guthaus, J. Renau, “Timing Speculative SRAM”, IEEE In- ternational Symposium on Circuits and Systems (ISCAS), 2017.](https://escholarship.org/content/qt7nn0j5x3/qt7nn0j5x3_noSplash_172457455e1aceba20694c3d7aa489b4.pdf)
+ [E. Ebrahimi, M. Guthaus, J. Renau, “Timing Speculative SRAM”, IEEE International Symposium on Circuits and Systems (ISCAS), 2017.](https://escholarship.org/content/qt7nn0j5x3/qt7nn0j5x3_noSplash_172457455e1aceba20694c3d7aa489b4.pdf)
+ [B. Wu, J.E. Stine, M.R. Guthaus, "Fast and Area-Efficient Word-Line Optimization", IEEE International Symposium on Circuits and Systems (ISCAS), 2019.](https://escholarship.org/content/qt98s4c1hp/qt98s4c1hp_noSplash_753dcc3e218f60aafff98ef77fb56384.pdf)
+ [B. Wu, M. Guthaus, "Bottom Up Approach for High Speed SRAM Word-line Buffer Insertion Optimization", IFIP/IEEE International Conference on Very Large Scale Integration (VLSI-SoC), 2019.](https://ieeexplore.ieee.org/document/8920325)
+ [H. Nichols, M. Grimes, J. Sowash, J. Cirimelli-Low, M. Guthaus "Automated Synthesis of Multi-Port Memories and Control", IFIP/IEEE International Conference on Very Large Scale Integration (VLSI-SoC), 2019.](https://escholarship.org/content/qt7047n3k0/qt7047n3k0.pdf?t=q4gcij)

View File

@ -1 +1 @@
1.2.8
1.2.15

View File

@ -7,43 +7,85 @@
#
import os
# Attempt to add the source code to the PYTHONPATH here before running globals.init_openram().
# Attempt to add the source code to the PYTHONPATH here before running globals.init_openram()
try:
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
except:
OPENRAM_HOME = os.path.dirname(os.path.abspath(__file__)) + "/compiler"
if not os.path.isdir(OPENRAM_HOME):
assert False
# Make sure that OPENRAM_HOME is an environment variable just in case
if "OPENRAM_HOME" not in os.environ.keys():
os.environ["OPENRAM_HOME"] = OPENRAM_HOME
# Prepend $OPENRAM_HOME to __path__ so that openram will use those modules
__path__.insert(0, OPENRAM_HOME)
# Find the conda installation directory
# Find the conda installer script
if os.path.exists(OPENRAM_HOME + "/install_conda.sh"):
CONDA_INSTALLER = OPENRAM_HOME + "/install_conda.sh"
CONDA_HOME = OPENRAM_HOME + "/miniconda"
elif os.path.exists(OPENRAM_HOME + "/../install_conda.sh"):
CONDA_INSTALLER = OPENRAM_HOME + "/../install_conda.sh"
CONDA_HOME = os.path.abspath(OPENRAM_HOME + "/../miniconda")
# Add CONDA_HOME to environment variables
# Override CONDA_HOME if it's set as an environment variable
if "CONDA_HOME" in os.environ.keys():
CONDA_HOME = os.environ["CONDA_HOME"]
# Add CONDA_HOME to environment variables just in case
try:
os.environ["CONDA_HOME"] = CONDA_HOME
except:
from openram import debug
debug.warning("Couldn't find install_conda.sh")
debug.warning("Couldn't find conda setup directory.")
# Import everything in globals.py
from .globals import *
# Import classes in the "openram" namespace
# sram_config should be imported before sram
from .sram_config import *
from .sram import *
from .rom_config import *
from .rom import *
# Add a meta path finder for custom modules
from importlib.abc import MetaPathFinder
class custom_module_finder(MetaPathFinder):
"""
This class is a 'hook' in Python's import system. If it encounters a module
that can be customized, it checks if there is a custom module specified in
the configuration file. If there is a custom module, it is imported instead
of the default one.
"""
def find_spec(self, fullname, path, target=None):
# Get package and module names
package_name = fullname.split(".")[0]
module_name = fullname.split(".")[-1]
# Skip if the package is not openram
if package_name != "openram":
return None
# Search for the module name in customizable modules
from openram import OPTS
for k, v in OPTS.__dict__.items():
if module_name == v:
break
else:
return None
# Search for the custom module
import sys
# Try to find the module in sys.path
for path in sys.path:
# Skip this path if not directory
if not os.path.isdir(path):
continue
for file in os.listdir(path):
# If there is a script matching the custom module name,
# import it with the default module name
if file == (module_name + ".py"):
from importlib.util import spec_from_file_location
return spec_from_file_location(module_name, "{0}/{1}.py".format(path, module_name))
return None
# Python calls meta path finders and asks them to handle the module import if
# they can
sys.meta_path.insert(0, custom_module_finder())

View File

@ -28,30 +28,26 @@ NAME = "OpenRAM v{}".format(VERSION)
USAGE = "sram_compiler.py [options] <config file>\nUse -h for help.\n"
OPTS = options.options()
CHECKPOINT_OPTS = None
def parse_args():
""" Parse the optional arguments for OpenRAM """
""" Parse the optional arguments for OpenRAM. """
global OPTS
option_list = {
optparse.make_option("-b",
"--backannotated",
optparse.make_option("-b", "--backannotated",
action="store_true",
dest="use_pex",
help="Back annotate simulation"),
optparse.make_option("-o",
"--output",
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",
optparse.make_option("-i", "--inlinecheck",
action="store_true",
help="Enable inline LVS/DRC checks",
dest="inline_lvsdrc"),
@ -69,36 +65,29 @@ def parse_args():
type="int",
help="Specify the number of spice simulation threads (default: 3)",
dest="num_sim_threads"),
optparse.make_option("-v",
"--verbose",
optparse.make_option("-v", "--verbose",
action="count",
dest="verbose_level",
help="Increase the verbosity level"),
optparse.make_option("-t",
"--tech",
optparse.make_option("-t", "--tech",
dest="tech_name",
help="Technology name"),
optparse.make_option("-s",
"--spice",
optparse.make_option("-s", "--spice",
dest="spice_name",
help="Spice simulator executable name"),
optparse.make_option("-r",
"--remove_netlist_trimming",
optparse.make_option("-r", "--remove_netlist_trimming",
action="store_false",
dest="trim_netlist",
help="Disable removal of noncritical memory cells during characterization"),
optparse.make_option("-c",
"--characterize",
optparse.make_option("-c", "--characterize",
action="store_false",
dest="analytical_delay",
help="Perform characterization to calculate delays (default is analytical models)"),
optparse.make_option("-k",
"--keeptemp",
optparse.make_option("-k", "--keeptemp",
action="store_true",
dest="keep_temp",
help="Keep the contents of the temp directory after a successful run"),
optparse.make_option("-d",
"--debug",
optparse.make_option("-d", "--debug",
action="store_true",
dest="debug",
help="Run in debug mode to drop to pdb on failure")
@ -126,7 +115,7 @@ def parse_args():
def print_banner():
""" Conditionally print the banner to stdout """
""" Conditionally print the banner to stdout. """
global OPTS
if OPTS.is_unit_test:
return
@ -161,8 +150,7 @@ def check_versions():
try:
subprocess.check_output(["git", "--version"])
except:
debug.error("Git is required. Please install git.")
sys.exit(1)
debug.error("Git is required. Please install git.", -1)
# FIXME: Check versions of other tools here??
# or, this could be done in each module (e.g. verify, characterizer, etc.)
@ -209,17 +197,6 @@ def init_openram(config_file, is_unit_test=False):
factory.reset()
global OPTS
global CHECKPOINT_OPTS
# This is a hack. If we are running a unit test and have checkpointed
# the options, load them rather than reading the config file.
# This way, the configuration is reloaded at the start of every unit test.
# If a unit test fails,
# we don't have to worry about restoring the old config values
# that may have been tested.
if is_unit_test and CHECKPOINT_OPTS:
OPTS.__dict__ = CHECKPOINT_OPTS.__dict__.copy()
return
# Setup correct bitcell names
setup_bitcell()
@ -227,10 +204,6 @@ def init_openram(config_file, is_unit_test=False):
# Import these to find the executables for checkpointing
from openram import characterizer
from openram import verify
# Make a checkpoint of the options so we can restore
# after each unit test
if not CHECKPOINT_OPTS:
CHECKPOINT_OPTS = copy.copy(OPTS)
def install_conda():
@ -242,8 +215,8 @@ def install_conda():
debug.info(1, "Creating conda setup...");
from openram import CONDA_HOME
subprocess.call("./install_conda.sh", cwd=os.path.abspath(CONDA_HOME + "/.."))
from openram import CONDA_INSTALLER
subprocess.call(CONDA_INSTALLER)
def setup_bitcell():
@ -296,18 +269,17 @@ def get_tool(tool_type, preferences, default_name=None):
2)
else:
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
return(default_name, exe_name)
return (default_name, exe_name)
else:
for name in preferences:
exe_name = find_exe(name)
if exe_name != None:
debug.info(1, "Using {0}: {1}".format(tool_type, exe_name))
return(name, exe_name)
return (name, exe_name)
else:
debug.info(1,
"Could not find {0}, trying next {1} tool.".format(name, tool_type))
debug.info(1, "Could not find {0}, trying next {1} tool.".format(name, tool_type))
else:
return(None, "")
return (None, "")
def read_config(config_file, is_unit_test=False):
@ -397,7 +369,7 @@ def read_config(config_file, is_unit_test=False):
def end_openram():
""" Clean up openram for a proper exit """
""" Clean up openram for a proper exit. """
cleanup_paths()
if OPTS.check_lvsdrc:
@ -409,8 +381,7 @@ def end_openram():
def purge_temp():
""" Remove the temp directory. """
debug.info(1,
"Purging temp directory: {}".format(OPTS.openram_temp))
debug.info(1, "Purging temp directory: {}".format(OPTS.openram_temp))
#import inspect
#s = inspect.stack()
#print("Purge {0} in dir {1}".format(s[3].filename, OPTS.openram_temp))
@ -432,8 +403,7 @@ def cleanup_paths():
"""
global OPTS
if OPTS.keep_temp:
debug.info(0,
"Preserving temp directory: {}".format(OPTS.openram_temp))
debug.info(0, "Preserving temp directory: {}".format(OPTS.openram_temp))
return
elif os.path.exists(OPTS.openram_temp):
purge_temp()
@ -450,7 +420,6 @@ def setup_paths():
# Use a unique temp subdirectory if multithreaded
if OPTS.num_threads > 1 or OPTS.openram_temp == "/tmp":
# Make a unique subdir
tempdir = "/openram_{0}_{1}_temp".format(getpass.getuser(),
os.getpid())
@ -465,14 +434,15 @@ def setup_paths():
def is_exe(fpath):
""" Return true if the given is an executable file that exists. """
return os.path.exists(fpath) and os.access(fpath, os.X_OK)
def find_exe(check_exe):
"""
Check if the binary exists in any path dir
and return the full path.
Check if the binary exists in any path dir and return the full path.
"""
# Search for conda setup if used
if OPTS.use_conda:
from openram import CONDA_HOME
@ -481,6 +451,7 @@ def find_exe(check_exe):
os.environ["PATH"])
else:
search_path = os.environ["PATH"]
# Check if the preferred spice option exists in the path
for path in search_path.split(os.pathsep):
exe = os.path.join(path, check_exe)
@ -491,18 +462,20 @@ def find_exe(check_exe):
def init_paths():
""" Create the temp and output directory if it doesn't exist """
""" Create the temp and output directory if it doesn't exist. """
if os.path.exists(OPTS.openram_temp):
purge_temp()
else:
# make the directory if it doesn't exist
# Make the directory if it doesn't exist
try:
debug.info(1,
"Creating temp directory: {}".format(OPTS.openram_temp))
debug.info(1, "Creating temp directory: {}".format(OPTS.openram_temp))
os.makedirs(OPTS.openram_temp, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.openram_temp, 0o750)
else:
debug.error("Unable to make temp directory: {}".format(OPTS.openram_temp), -1)
#import inspect
#s = inspect.stack()
#from pprint import pprint
@ -515,10 +488,10 @@ def init_paths():
try:
os.makedirs(OPTS.output_path, 0o750)
except OSError as e:
if e.errno == 17: # errno.EEXIST
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.output_path, 0o750)
except:
debug.error("Unable to make output directory.", -1)
else:
debug.error("Unable to make output directory: {}".format(OPTS.output_path), -1)
def set_default_corner():
@ -581,13 +554,14 @@ def import_tech():
debug.info(1, "Tech directory found in {}".format(OPENRAM_TECH))
# Add this environment variable to os.environ
# Add this environment variable to os.environ and openram namespace
os.environ["OPENRAM_TECH"] = OPENRAM_TECH
openram.OPENRAM_TECH = OPENRAM_TECH
# Add all of the paths
for tech_path in OPENRAM_TECH.split(":"):
debug.check(os.path.isdir(tech_path),
"$OPENRAM_TECH does not exist: {0}".format(tech_path))
"$OPENRAM_TECH does not exist: {}".format(tech_path))
sys.path.append(tech_path)
debug.info(1, "Adding technology path: {}".format(tech_path))
@ -595,7 +569,7 @@ def import_tech():
try:
tech_mod = __import__(OPTS.tech_name)
except ImportError:
debug.error("Nonexistent technology module: {0}".format(OPTS.tech_name), -1)
debug.error("Nonexistent technology module: {}".format(OPTS.tech_name), -1)
OPTS.openram_tech = os.path.dirname(tech_mod.__file__) + "/"
@ -664,7 +638,7 @@ def report_status():
total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks
debug.print_raw("Total size: {} bits".format(total_size))
if total_size >= 2**14 and not OPTS.analytical_delay:
debug.warning("Characterizing large memories ({0}) will have a large run-time. ".format(total_size))
debug.warning("Characterizing large memories ({0}) will have a large run-time.".format(total_size))
debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
OPTS.num_words,
OPTS.num_banks))

View File

@ -4,7 +4,6 @@ include $(TOP_DIR)/openram.mk
.DEFAULT_GOAL := all
ARGS ?=
TEST_TECHS ?= scn4m_subm freepdk45
TECHS ?= scn4m_subm freepdk45 sky130
TEST_DIR = $(TOP_DIR)/compiler/tests
@ -107,11 +106,65 @@ BROKEN_STAMPS = \
sky130/23_lib_sram_model_corners_test.ok \
sky130/23_lib_sram_model_test.ok \
sky130/23_lib_sram_prune_test.ok \
sky131/23_lib_sram_test.ok
sky130/23_lib_sram_test.ok \
sky130/03_wire_test.ok \
sky130/04_dummy_pbitcell_1rw1r1w_test.ok \
sky130/04_dummy_pbitcell_1rw_test.ok \
sky130/04_replica_pbitcell_1rw1r1w_test.ok \
sky130/04_replica_pbitcell_1rw_test.ok \
sky130/06_hierarchical_decoder_132row_test.ok \
sky130/06_hierarchical_decoder_512row_test.ok \
sky130/06_hierarchical_decoder_64row_test.ok \
sky130/06_hierarchical_decoder_pbitcell_test.ok \
sky130/10_write_driver_array_spare_cols_test.ok \
sky130/10_write_driver_array_wmask_spare_cols_test.ok \
sky130/14_capped_replica_bitcell_array_leftrbl_1rw_test.ok \
sky130/14_capped_replica_bitcell_array_norbl_1rw_test.ok \
sky130/14_replica_bitcell_array_leftrbl_1rw_test.ok \
sky130/14_replica_bitcell_array_norbl_1rw_test.ok \
sky130/14_replica_column_1rw_1r_test.ok \
sky130/14_replica_column_1rw_test.ok \
sky130/14_replica_pbitcell_1rw1r_array_test.ok \
sky130/14_replica_pbitcell_1rw_array_test.ok \
sky130/15_global_bitcell_array_1rw_1r_test.ok \
sky130/15_global_bitcell_array_test.ok \
sky130/15_local_bitcell_array_test.ok \
sky130/18_port_address_512rows_test.ok \
sky130/18_port_data_spare_cols_test.ok \
sky130/19_single_bank_2mux_test.ok \
sky130/19_single_bank_4mux_test.ok \
sky130/19_single_bank_8mux_test.ok \
sky130/19_single_bank_global_bitline_test.ok \
sky130/19_single_bank_nomux_test.ok \
sky130/19_single_bank_spare_cols_test.ok \
sky130/19_single_bank_wmask_test.ok \
sky130/20_sram_1bank_2mux_1rw_1r_spare_cols_test.ok \
sky130/20_sram_1bank_2mux_1w_1r_spare_cols_test.ok \
sky130/20_sram_1bank_2mux_global_test.ok \
sky130/20_sram_1bank_2mux_test.ok \
sky130/20_sram_1bank_2mux_wmask_spare_cols_test.ok \
sky130/20_sram_1bank_2mux_wmask_test.ok \
sky130/20_sram_1bank_4mux_test.ok \
sky130/20_sram_1bank_8mux_test.ok \
sky130/20_sram_1bank_nomux_spare_cols_test.ok \
sky130/20_sram_1bank_nomux_test.ok \
sky130/20_sram_1bank_nomux_wmask_test.ok \
sky130/20_sram_1bank_ring_test.ok \
sky130/21_model_delay_test.ok \
sky130/21_ngspice_delay_extra_rows_test.ok \
sky130/21_ngspice_delay_test.ok \
sky130/21_regression_delay_test.ok \
sky130/21_xyce_delay_test.ok \
sky130/25_verilog_multibank_test.ok \
sky130/25_verilog_sram_test.ok \
sky130/30_openram_back_end_library_test.ok \
sky130/30_openram_back_end_test.ok \
sky130/30_openram_front_end_library_test.ok \
sky130/30_openram_front_end_test.ok \
gettech = $(word 1,$(subst /, ,$*))
getfile = $(word 2,$(subst /, ,$*))
TECH_TEST_STAMPS=$(foreach T, $(TEST_TECHS), $(addprefix $T/, $(TEST_STAMPS)))
TECH_TEST_STAMPS=$(foreach T, $(TECHS), $(addprefix $T/, $(TEST_STAMPS)))
# Filter out the tests after creating the tech stamps
WORKING_TECH_TEST_STAMPS=$(shell shuf -e -- $(filter-out $(BROKEN_STAMPS), $(TECH_TEST_STAMPS)))

329788
docs/source/OpenRAM.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@ -30,16 +30,38 @@ you don't have to worry about updating/installing these tools. OpenRAM installs
Anaconda silently in the background (without affecting any existing Anaconda
setup you have).
You don't have to manually activate/deactivate the Anaconda environment. OpenRAM
automatically manages this before and after running the tools.
OpenRAM uses Anaconda by default, but you can turn this feature off by setting
`use_conda = False` in your config file. Then, OpenRAM will use the tools you
have installed on your system.
You can also tell OpenRAM where Anaconda should be installed or which Anaconda
setup it should use. You can set the `$CONDA_HOME` variable like this:
```
export CONDA_HOME="/path/to/conda/setup"
```
> **Note**: If you want to install Anaconda without running OpenRAM (for example
> to run unit tests, which do not install Anaconda), you can run:
> ```
> ./install_conda.sh
> ```
> **Note**: You can uninstall OpenRAM's Anaconda installation by simply deleting
> the folder Anaconda is installed to. You can run:
> ```
> rm -rf miniconda
> ```
> **Note**: You can change a tool's version with the following commands:
> ```
> source ./miniconda/bin/activate
> conda uninstall <tool>
> conda install -y -c vlsida-eda <tool>=<version>
> ```
## Docker (deprecated, use Anaconda instead)

View File

@ -9,6 +9,7 @@ navigate through the documentation.
## Table of Contents
1. [OpenRAM Dependencies](#openram-dependencies)
1. [Supported Technologies](#supported-technologies)
1. [Online Playground](./OpenRAM.ipynb)
1. [Basic Setup](./basic_setup.md#go-back)
1. [Basic Usage](./basic_usage.md#go-back)
1. [Python Library](./python_library.md#go-back)

View File

@ -47,6 +47,13 @@ point to that OpenRAM installation directory.
If you don't want to use this feature, you can simply unset these environment
variables.
> **Note**: If you are a developer working on the source code on local clone of
> the repository and want to use the Python library at the same time, you should
> set both `OPENRAM_HOME` and `OPENRAM_TECH` to point to the local clone (follow
> [Basic Setup](./basic_setup.md#go-back)). This way, the library will use the
> source code located at these paths and you won't have to rebuild the library
> after every change.
## Usage

View File

@ -1,17 +1,17 @@
#!/bin/bash
CONDA_INSTALLER_URL="https://repo.anaconda.com/miniconda/Miniconda3-py38_22.11.1-1-Linux-x86_64.sh"
CONDA_INSTALLER_FILE="miniconda_installer_py38.sh"
CONDA_BASE="miniconda"
CONDA_HOME="${CONDA_HOME:-miniconda}"
TOOLS="klayout magic netgen ngspice trilinos xyce"
# Install miniconda if not installed
if [ ! -d "miniconda" ]
# Install miniconda if not already installed
if [[ ! -d "${CONDA_HOME}/bin" ]]
then
curl -s -o ${CONDA_INSTALLER_FILE} ${CONDA_INSTALLER_URL}
/bin/bash ${CONDA_INSTALLER_FILE} -b -p ${CONDA_BASE}
/bin/bash ${CONDA_INSTALLER_FILE} -b -p ${CONDA_HOME}
rm ${CONDA_INSTALLER_FILE}
source ${CONDA_BASE}/bin/activate
source ${CONDA_HOME}/bin/activate
# Prioritize channels to prevent version conflicts
conda config --add channels conda-forge
@ -26,6 +26,10 @@ then
conda install -q -y -c vlsida-eda ${tool}
done
# Install required Python packages
# (This step isn't required but used to prevent possible issues)
python3 -m pip install -r requirements.txt
conda deactivate
fi

View File

@ -0,0 +1,731 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram import debug
from openram.base import vector
from openram.base import contact
from openram.sram_factory import factory
from openram.tech import drc, spice
from openram.tech import cell_properties as props
from openram import OPTS
from openram.modules import bitcell_base_array
class replica_bitcell_array(bitcell_base_array):
"""
Creates a bitcell array of cols x rows and then adds the replica
and dummy columns and rows. Replica columns are on the left and
right, respectively and connected to the given bitcell ports.
Dummy are the outside columns/rows with WL and BL tied to gnd.
Requires a regular bitcell array, replica bitcell, and dummy
bitcell (BL/BR disconnected).
"""
def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""):
super().__init__(name=name, rows=rows, cols=cols, column_offset=0)
debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name,
rows,
cols,
rbl,
left_rbl,
right_rbl))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.add_comment("rbl: {0} left_rbl: {1} right_rbl: {2}".format(rbl, left_rbl, right_rbl))
self.column_size = cols
self.row_size = rows
# This is how many RBLs are in all the arrays
if rbl:
self.rbl = rbl
else:
self.rbl=[1, 1 if len(self.all_ports)>1 else 0]
# This specifies which RBL to put on the left or right
# by port number
# This could be an empty list
if left_rbl != None:
self.left_rbl = left_rbl
else:
self.left_rbl = [0]
# This could be an empty list
if right_rbl != None:
self.right_rbl = right_rbl
else:
self.right_rbl=[1] if len(self.all_ports) > 1 else []
self.rbls = self.left_rbl + self.right_rbl
debug.check(sum(self.rbl) == len(self.all_ports),
"Invalid number of RBLs for port configuration.")
debug.check(sum(self.rbl) >= len(self.left_rbl) + len(self.right_rbl),
"Invalid number of RBLs for port configuration.")
# Two dummy rows plus replica even if we don't add the column
self.extra_rows = sum(self.rbl)
# Two dummy cols plus replica if we add the column
self.extra_cols = len(self.left_rbl) + len(self.right_rbl)
# If we aren't using row/col caps, then we need to use the bitcell
if not self.cell.end_caps:
self.extra_rows += 2
self.extra_cols += 2
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
# We don't offset this because we need to align
# the replica bitcell in the control logic
# self.offset_all_coordinates()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
def add_modules(self):
""" Array and dummy/replica columns
d or D = dummy cell (caps to distinguish grouping)
r or R = replica cell (caps to distinguish grouping)
b or B = bitcell
replica columns 1
v v
bdDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDrb <- Dummy row
br--------------rb
br| Array |rb
br| row x col |rb
br--------------rb
brDDDDDDDDDDDDDDdb <- Dummy row
bdDDDDDDDDDDDDDDdb <- Dummy row
^^^^^^^^^^^^^^^
dummy rows cols x 1
^ dummy columns ^
1 x (rows + 4)
"""
# Bitcell array
self.bitcell_array = factory.create(module_type="bitcell_array",
column_offset=1 + len(self.left_rbl),
cols=self.column_size,
rows=self.row_size)
# Replica bitlines
self.replica_columns = {}
for port in self.all_ports:
if port in self.left_rbl:
# We will always have self.rbl[0] rows of replica wordlines below
# the array.
# These go from the top (where the bitcell array starts ) down
replica_bit = self.rbl[0] - port
column_offset = self.rbl[0]
elif port in self.right_rbl:
# We will always have self.rbl[0] rows of replica wordlines below
# the array.
# These go from the bottom up
replica_bit = self.rbl[0] + self.row_size + port
column_offset = self.rbl[0] + self.column_size + 1
else:
continue
self.replica_columns[port] = factory.create(module_type="replica_column",
rows=self.row_size,
rbl=self.rbl,
column_offset=column_offset,
replica_bit=replica_bit)
# Dummy row
self.dummy_row = factory.create(module_type="dummy_array",
cols=self.column_size,
rows=1,
# dummy column + left replica column
column_offset=1 + len(self.left_rbl),
mirror=0)
# Dummy Row or Col Cap, depending on bitcell array properties
col_cap_module_type = ("col_cap_array" if self.cell.end_caps else "dummy_array")
self.col_cap_top = factory.create(module_type=col_cap_module_type,
cols=self.column_size,
rows=1,
# dummy column + left replica column(s)
column_offset=1 + len(self.left_rbl),
mirror=0,
location="top")
self.col_cap_bottom = factory.create(module_type=col_cap_module_type,
cols=self.column_size,
rows=1,
# dummy column + left replica column(s)
column_offset=1 + len(self.left_rbl),
mirror=0,
location="bottom")
# Dummy Col or Row Cap, depending on bitcell array properties
row_cap_module_type = ("row_cap_array" if self.cell.end_caps else "dummy_array")
self.row_cap_left = factory.create(module_type=row_cap_module_type,
cols=1,
column_offset=0,
rows=self.row_size + self.extra_rows,
mirror=(self.rbl[0] + 1) % 2)
self.row_cap_right = factory.create(module_type=row_cap_module_type,
cols=1,
# dummy column
# + left replica column(s)
# + bitcell columns
# + right replica column(s)
column_offset=1 + len(self.left_rbl) + self.column_size + self.rbl[0],
rows=self.row_size + self.extra_rows,
mirror=(self.rbl[0] + 1) %2)
def add_pins(self):
# Arrays are always:
# bitlines (column first then port order)
# word lines (row first then port order)
# dummy wordlines
# replica wordlines
# regular wordlines (bottom to top)
# # dummy bitlines
# replica bitlines (port order)
# regular bitlines (left to right port order)
#
# vdd
# gnd
self.add_bitline_pins()
self.add_wordline_pins()
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_bitline_pins(self):
# The bit is which port the RBL is for
for bit in self.rbls:
for port in self.all_ports:
self.rbl_bitline_names[bit].append("rbl_bl_{0}_{1}".format(port, bit))
for port in self.all_ports:
self.rbl_bitline_names[bit].append("rbl_br_{0}_{1}".format(port, bit))
# Make a flat list too
self.all_rbl_bitline_names = [x for sl in self.rbl_bitline_names for x in sl]
self.bitline_names = self.bitcell_array.bitline_names
# Make a flat list too
self.all_bitline_names = [x for sl in zip(*self.bitline_names) for x in sl]
for port in self.left_rbl:
self.add_pin_list(self.rbl_bitline_names[port], "INOUT")
self.add_pin_list(self.all_bitline_names, "INOUT")
for port in self.right_rbl:
self.add_pin_list(self.rbl_bitline_names[port], "INOUT")
def add_wordline_pins(self):
# Wordlines to ground
self.gnd_wordline_names = []
for port in self.all_ports:
for bit in self.all_ports:
self.rbl_wordline_names[port].append("rbl_wl_{0}_{1}".format(port, bit))
if bit != port:
self.gnd_wordline_names.append("rbl_wl_{0}_{1}".format(port, bit))
self.all_rbl_wordline_names = [x for sl in self.rbl_wordline_names for x in sl]
self.wordline_names = self.bitcell_array.wordline_names
self.all_wordline_names = self.bitcell_array.all_wordline_names
# All wordlines including dummy and RBL
self.replica_array_wordline_names = []
self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap_top.get_wordline_names()))
for bit in range(self.rbl[0]):
self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[bit]])
self.replica_array_wordline_names.extend(self.all_wordline_names)
for bit in range(self.rbl[1]):
self.replica_array_wordline_names.extend([x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[self.rbl[0] + bit]])
self.replica_array_wordline_names.extend(["gnd"] * len(self.col_cap_top.get_wordline_names()))
for port in range(self.rbl[0]):
self.add_pin(self.rbl_wordline_names[port][port], "INPUT")
self.add_pin_list(self.all_wordline_names, "INPUT")
for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]):
self.add_pin(self.rbl_wordline_names[port][port], "INPUT")
def create_instances(self):
""" Create the module instances used in this design """
self.supplies = ["vdd", "gnd"]
# Used for names/dimensions only
self.cell = factory.create(module_type=OPTS.bitcell)
# Main array
self.bitcell_array_inst=self.add_inst(name="bitcell_array",
mod=self.bitcell_array)
self.connect_inst(self.all_bitline_names + self.all_wordline_names + self.supplies)
# Replica columns
self.replica_col_insts = []
for port in self.all_ports:
if port in self.rbls:
self.replica_col_insts.append(self.add_inst(name="replica_col_{}".format(port),
mod=self.replica_columns[port]))
self.connect_inst(self.rbl_bitline_names[port] + self.replica_array_wordline_names + self.supplies)
else:
self.replica_col_insts.append(None)
# Dummy rows under the bitcell array (connected with with the replica cell wl)
self.dummy_row_replica_insts = []
# Note, this is the number of left and right even if we aren't adding the columns to this bitcell array!
for port in self.all_ports:
self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port),
mod=self.dummy_row))
self.connect_inst(self.all_bitline_names + [x if x not in self.gnd_wordline_names else "gnd" for x in self.rbl_wordline_names[port]] + self.supplies)
# Top/bottom dummy rows or col caps
self.dummy_row_insts = []
self.dummy_row_insts.append(self.add_inst(name="dummy_row_bot",
mod=self.col_cap_bottom))
self.connect_inst(self.all_bitline_names + ["gnd"] * len(self.col_cap_bottom.get_wordline_names()) + self.supplies)
self.dummy_row_insts.append(self.add_inst(name="dummy_row_top",
mod=self.col_cap_top))
self.connect_inst(self.all_bitline_names + ["gnd"] * len(self.col_cap_top.get_wordline_names()) + self.supplies)
# Left/right Dummy columns
self.dummy_col_insts = []
self.dummy_col_insts.append(self.add_inst(name="dummy_col_left",
mod=self.row_cap_left))
self.connect_inst(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.replica_array_wordline_names + self.supplies)
self.dummy_col_insts.append(self.add_inst(name="dummy_col_right",
mod=self.row_cap_right))
self.connect_inst(["dummy_right_" + bl for bl in self.row_cap_right.all_bitline_names] + self.replica_array_wordline_names + self.supplies)
def create_layout(self):
# This creates space for the unused wordline connections as well as the
# row-based or column based power and ground lines.
self.vertical_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[0]))
self.horizontal_pitch = 1.1 * getattr(self, "{}_pitch".format(self.supply_stack[2]))
self.unused_offset = vector(0.25, 0.25)
# This is a bitcell x bitcell offset to scale
self.bitcell_offset = vector(self.cell.width, self.cell.height)
self.col_end_offset = vector(self.cell.width, self.cell.height)
self.row_end_offset = vector(self.cell.width, self.cell.height)
# Everything is computed with the main array
self.bitcell_array_inst.place(offset=self.unused_offset)
self.add_replica_columns()
self.add_end_caps()
# Array was at (0, 0) but move everything so it is at the lower left
# We move DOWN the number of left RBL even if we didn't add the column to this bitcell array
# Note that this doesn't include the row/col cap
array_offset = self.bitcell_offset.scale(1 + len(self.left_rbl), 1 + self.rbl[0])
self.translate_all(array_offset.scale(-1, -1))
# Add extra width on the left and right for the unused WLs
self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x
self.height = self.dummy_row_insts[1].uy()
self.add_layout_pins()
self.route_supplies()
self.route_unused_wordlines()
lower_left = self.find_lowest_coords()
upper_right = self.find_highest_coords()
self.width = upper_right.x - lower_left.x
self.height = upper_right.y - lower_left.y
self.translate_all(lower_left)
self.add_boundary()
self.DRC_LVS()
def get_main_array_top(self):
""" Return the top of the main bitcell array. """
return self.bitcell_array_inst.uy()
def get_main_array_bottom(self):
""" Return the bottom of the main bitcell array. """
return self.bitcell_array_inst.by()
def get_main_array_left(self):
""" Return the left of the main bitcell array. """
return self.bitcell_array_inst.lx()
def get_main_array_right(self):
""" Return the right of the main bitcell array. """
return self.bitcell_array_inst.rx()
def get_replica_top(self):
""" Return the top of all replica columns. """
return self.dummy_row_insts[0].by()
def get_replica_bottom(self):
""" Return the bottom of all replica columns. """
return self.dummy_row_insts[0].uy()
def get_replica_left(self):
""" Return the left of all replica columns. """
return self.dummy_col_insts[0].rx()
def get_replica_right(self):
""" Return the right of all replica columns. """
return self.dummy_col_insts[1].rx()
def get_column_offsets(self):
"""
Return an array of the x offsets of all the regular bits
"""
offsets = [x + self.bitcell_array_inst.lx() for x in self.bitcell_array.get_column_offsets()]
return offsets
def add_replica_columns(self):
""" Add replica columns on left and right of array """
# Grow from left to right, toward the array
for bit, port in enumerate(self.left_rbl):
offset = self.bitcell_offset.scale(-len(self.left_rbl) + bit, -self.rbl[0] - 1) + self.unused_offset
self.replica_col_insts[bit].place(offset)
# Grow to the right of the bitcell array, array outward
for bit, port in enumerate(self.right_rbl):
offset = self.bitcell_array_inst.lr() + self.bitcell_offset.scale(bit, -self.rbl[0] - 1)
self.replica_col_insts[self.rbl[0] + bit].place(offset)
# Replica dummy rows
# Add the dummy rows even if we aren't adding the replica column to this bitcell array
# These grow up, toward the array
for bit in range(self.rbl[0]):
dummy_offset = self.bitcell_offset.scale(0, -self.rbl[0] + bit + (-self.rbl[0] + bit) % 2) + self.unused_offset
self.dummy_row_replica_insts[bit].place(offset=dummy_offset,
mirror="MX" if (-self.rbl[0] + bit) % 2 else "R0")
# These grow up, away from the array
for bit in range(self.rbl[1]):
dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul()
self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset,
mirror="MX" if (self.row_size + bit) % 2 else "R0")
def add_end_caps(self):
""" Add dummy cells or end caps around the array """
# Far top dummy row (first row above array is NOT flipped if even number of rows)
flip_dummy = (self.row_size + self.rbl[1]) % 2
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul()
self.dummy_row_insts[1].place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# Far bottom dummy row (first row below array IS flipped)
flip_dummy = (self.rbl[0] + 1) % 2
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset
self.dummy_row_insts[0].place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# Far left dummy col
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
dummy_col_offset = self.bitcell_offset.scale(-len(self.left_rbl) - 1, -self.rbl[0] - 1) + self.unused_offset
self.dummy_col_insts[0].place(offset=dummy_col_offset)
# Far right dummy col
# Shifted down by the number of left RBLs even if we aren't adding replica column to this bitcell array
dummy_col_offset = self.bitcell_offset.scale(len(self.right_rbl), -self.rbl[0] - 1) + self.bitcell_array_inst.lr()
self.dummy_col_insts[1].place(offset=dummy_col_offset)
def add_layout_pins(self):
""" Add the layout pins """
# All wordlines
# Main array wl and bl/br
for pin_name in self.all_wordline_names:
pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
# Replica wordlines (go by the row instead of replica column because we may have to add a pin
# even though the column is in another local bitcell array)
for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts):
for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()):
if wl_name in self.gnd_wordline_names:
continue
pin = inst.get_pin(pin_name)
self.add_layout_pin(text=wl_name,
layer=pin.layer,
offset=pin.ll().scale(0, 1),
width=self.width,
height=pin.height())
for pin_name in self.all_bitline_names:
pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
# Replica bitlines
if len(self.rbls) > 0:
for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts):
pin_names = self.replica_columns[self.rbls[0]].all_bitline_names
for (bl_name, pin_name) in zip(names, pin_names):
pin = inst.get_pin(pin_name)
self.add_layout_pin(text=bl_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
def route_supplies(self):
if OPTS.bitcell == "pbitcell":
bitcell = factory.create(module_type="pbitcell")
else:
bitcell = getattr(props, "bitcell_{}port".format(OPTS.num_ports))
vdd_dir = bitcell.vdd_dir
gnd_dir = bitcell.gnd_dir
# vdd/gnd are only connected in the perimeter cells
supply_insts = self.dummy_col_insts + self.dummy_row_insts
# For the wordlines
top_bot_mult = 1
left_right_mult = 1
# There are always vertical pins for the WLs on the left/right if we have unused wordlines
self.left_gnd_locs = self.route_side_pin("gnd", "left", left_right_mult)
self.right_gnd_locs = self.route_side_pin("gnd","right", left_right_mult)
# This needs to be big enough so that they aren't in the same supply routing grid
left_right_mult = 4
if gnd_dir == "V":
self.top_gnd_locs = self.route_side_pin("gnd", "top", top_bot_mult)
self.bot_gnd_locs = self.route_side_pin("gnd", "bot", top_bot_mult)
# This needs to be big enough so that they aren't in the same supply routing grid
top_bot_mult = 4
if vdd_dir == "V":
self.top_vdd_locs = self.route_side_pin("vdd", "top", top_bot_mult)
self.bot_vdd_locs = self.route_side_pin("vdd", "bot", top_bot_mult)
elif vdd_dir == "H":
self.left_vdd_locs = self.route_side_pin("vdd", "left", left_right_mult)
self.right_vdd_locs = self.route_side_pin("vdd", "right", left_right_mult)
else:
debug.error("Invalid vdd direction {}".format(vdd_dir), -1)
for inst in supply_insts:
for pin in inst.get_pins("vdd"):
if vdd_dir == "V":
self.connect_side_pin(pin, "top", self.top_vdd_locs[0].y)
self.connect_side_pin(pin, "bot", self.bot_vdd_locs[0].y)
elif vdd_dir == "H":
self.connect_side_pin(pin, "left", self.left_vdd_locs[0].x)
self.connect_side_pin(pin, "right", self.right_vdd_locs[0].x)
for inst in supply_insts:
for pin in inst.get_pins("gnd"):
if gnd_dir == "V":
self.connect_side_pin(pin, "top", self.top_gnd_locs[0].y)
self.connect_side_pin(pin, "bot", self.bot_gnd_locs[0].y)
elif gnd_dir == "H":
self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x)
self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x)
def route_unused_wordlines(self):
""" Connect the unused RBL and dummy wordlines to gnd """
# This grounds all the dummy row word lines
for inst in self.dummy_row_insts:
for wl_name in self.col_cap_top.get_wordline_names():
pin = inst.get_pin(wl_name)
self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x)
self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x)
# Ground the unused replica wordlines
for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts):
for (wl_name, pin_name) in zip(names, self.dummy_row.get_wordline_names()):
if wl_name in self.gnd_wordline_names:
pin = inst.get_pin(pin_name)
self.connect_side_pin(pin, "left", self.left_gnd_locs[0].x)
self.connect_side_pin(pin, "right", self.right_gnd_locs[0].x)
def route_side_pin(self, name, side, offset_multiple=1):
"""
Routes a vertical or horizontal pin on the side of the bbox.
The multiple specifies how many track offsets to be away from the side assuming
(0,0) (self.width, self.height)
"""
if side in ["left", "right"]:
return self.route_vertical_side_pin(name, side, offset_multiple)
elif side in ["top", "bottom", "bot"]:
return self.route_horizontal_side_pin(name, side, offset_multiple)
else:
debug.error("Invalid side {}".format(side), -1)
def route_vertical_side_pin(self, name, side, offset_multiple=1):
"""
Routes a vertical pin on the side of the bbox.
"""
if side == "left":
bot_loc = vector(-offset_multiple * self.vertical_pitch, 0)
top_loc = vector(-offset_multiple * self.vertical_pitch, self.height)
elif side == "right":
bot_loc = vector(self.width + offset_multiple * self.vertical_pitch, 0)
top_loc = vector(self.width + offset_multiple * self.vertical_pitch, self.height)
layer = self.supply_stack[2]
top_via = contact(layer_stack=self.supply_stack,
directions=("H", "H"))
# self.add_layout_pin_rect_ends(text=name,
# layer=layer,
# start=bot_loc,
# end=top_loc)
self.add_layout_pin_segment_center(text=name,
layer=layer,
start=bot_loc,
end=top_loc,
width=top_via.second_layer_width)
return (bot_loc, top_loc)
def route_horizontal_side_pin(self, name, side, offset_multiple=1):
"""
Routes a horizontal pin on the side of the bbox.
"""
if side in ["bottom", "bot"]:
left_loc = vector(0, -offset_multiple * self.horizontal_pitch)
right_loc = vector(self.width, -offset_multiple * self.horizontal_pitch)
elif side == "top":
left_loc = vector(0, self.height + offset_multiple * self.horizontal_pitch)
right_loc = vector(self.width, self.height + offset_multiple * self.horizontal_pitch)
layer = self.supply_stack[0]
side_via = contact(layer_stack=self.supply_stack,
directions=("V", "V"))
# self.add_layout_pin_rect_ends(text=name,
# layer=layer,
# start=left_loc,
# end=right_loc)
self.add_layout_pin_segment_center(text=name,
layer=layer,
start=left_loc,
end=right_loc,
width=side_via.first_layer_height)
return (left_loc, right_loc)
def connect_side_pin(self, pin, side, offset):
"""
Used to connect horizontal layers of pins to the left/right straps
locs provides the offsets of the pin strip end points.
"""
if side in ["left", "right"]:
self.connect_vertical_side_pin(pin, side, offset)
elif side in ["top", "bottom", "bot"]:
self.connect_horizontal_side_pin(pin, side, offset)
else:
debug.error("Invalid side {}".format(side), -1)
def connect_horizontal_side_pin(self, pin, side, yoffset):
"""
Used to connect vertical layers of pins to the top/bottom horizontal straps
"""
cell_loc = pin.center()
pin_loc = vector(cell_loc.x, yoffset)
# Place the pins a track outside of the array
self.add_via_stack_center(offset=pin_loc,
from_layer=pin.layer,
to_layer=self.supply_stack[0],
directions=("V", "V"))
# Add a path to connect to the array
self.add_path(pin.layer, [cell_loc, pin_loc])
def connect_vertical_side_pin(self, pin, side, xoffset):
"""
Used to connect vertical layers of pins to the top/bottom vertical straps
"""
cell_loc = pin.center()
pin_loc = vector(xoffset, cell_loc.y)
# Place the pins a track outside of the array
self.add_via_stack_center(offset=pin_loc,
from_layer=pin.layer,
to_layer=self.supply_stack[2],
directions=("H", "H"))
# Add a path to connect to the array
self.add_path(pin.layer, [cell_loc, pin_loc])
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
# Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(corner, load)
# Leakage power grows with entire array and bitlines.
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
cell_power.leakage * self.column_size * self.row_size)
return total_power
def gen_bl_wire(self):
if OPTS.netlist_only:
height = 0
else:
height = self.height
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def graph_exclude_bits(self, targ_row=None, targ_col=None):
"""
Excludes bits in column from being added to graph except target
"""
self.bitcell_array.graph_exclude_bits(targ_row, targ_col)
def graph_exclude_replica_col_bits(self):
"""
Exclude all replica/dummy cells in the replica columns except the replica bit.
"""
for port in self.left_rbl + self.right_rbl:
self.replica_columns[port].exclude_all_but_replica()
def get_cell_name(self, inst_name, row, col):
"""
Gets the spice name of the target bitcell.
"""
return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col)
def clear_exclude_bits(self):
"""
Clears the bit exclusions
"""
self.bitcell_array.init_graph_params()

View File

@ -0,0 +1,209 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram import debug
from openram.base import vector
from openram.base import contact
from openram.sram_factory import factory
from openram.tech import drc, spice
from openram.tech import cell_properties as props
from openram import OPTS
from .sky130_bitcell_base_array import sky130_bitcell_base_array
class sky130_capped_replica_bitcell_array(sky130_bitcell_base_array):
"""
Creates a replica bitcell array then adds the row and column caps to all
sides of a bitcell array.
"""
def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, name=""):
super().__init__(name, rows, cols, column_offset=0)
debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name,
rows,
cols,
rbl,
left_rbl,
right_rbl))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
self.add_comment("rbl: {0} left_rbl: {1} right_rbl: {2}".format(rbl, left_rbl, right_rbl))
self.rbl = rbl
self.left_rbl = left_rbl
self.right_rbl = right_rbl
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
def add_modules(self):
self.replica_bitcell_array = factory.create(module_type="replica_bitcell_array",
cols=self.column_size,
rows=self.row_size,
rbl=self.rbl,
left_rbl=self.left_rbl,
right_rbl=self.right_rbl)
def add_pins(self):
# Arrays are always:
# bitlines (column first then port order)
# word lines (row first then port order)
# dummy wordlines
# replica wordlines
# regular wordlines (bottom to top)
# # dummy bitlines
# replica bitlines (port order)
# regular bitlines (left to right port order)
#
# vdd
# gnd
self.add_bitline_pins()
self.add_wordline_pins()
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_bitline_pins(self):
self.bitline_names = self.replica_bitcell_array.bitline_names
self.all_bitline_names = self.replica_bitcell_array.all_bitline_names
self.rbl_bitline_names = self.replica_bitcell_array.rbl_bitline_names
self.all_rbl_bitline_names = self.replica_bitcell_array.all_rbl_bitline_names
self.bitline_pins = []
for port in self.left_rbl:
self.bitline_pins.extend(self.rbl_bitline_names[port])
self.bitline_pins.extend(self.all_bitline_names)
for port in self.right_rbl:
self.bitline_pins.extend(self.rbl_bitline_names[port])
self.add_pin_list(self.bitline_pins, "INOUT")
def add_wordline_pins(self):
# some of these are just included for compatibility with modules instantiating this module
self.rbl_wordline_names = self.replica_bitcell_array.rbl_wordline_names
self.all_rbl_wordline_names = self.replica_bitcell_array.all_rbl_wordline_names
self.wordline_names = self.replica_bitcell_array.wordline_names
self.all_wordline_names = self.replica_bitcell_array.all_wordline_names
self.wordline_pins = []
for port in range(self.rbl[0]):
self.wordline_pins.append(self.rbl_wordline_names[port][port])
self.wordline_pins.extend(self.all_wordline_names)
for port in range(self.rbl[0], self.rbl[0] + self.rbl[1]):
self.wordline_pins.append(self.rbl_wordline_names[port][port])
self.add_pin_list(self.wordline_pins, "INPUT")
def create_instances(self):
""" Create the module instances used in this design """
self.supplies = ["vdd", "gnd"]
# Main array
self.replica_bitcell_array_inst=self.add_inst(name="replica_bitcell_array",
mod=self.replica_bitcell_array)
self.connect_inst(self.bitline_pins + self.wordline_pins + self.supplies)
def create_layout(self):
self.replica_bitcell_array_inst.place(offset=0)
self.width = self.replica_bitcell_array.width
self.height = self.replica_bitcell_array.height
for pin_name in self.bitline_pins + self.wordline_pins + self.supplies:
self.copy_layout_pin(self.replica_bitcell_array_inst, pin_name)
self.add_boundary()
self.DRC_LVS()
def get_main_array_top(self):
return self.replica_bitcell_array.get_main_array_top()
def get_main_array_bottom(self):
return self.replica_bitcell_array.get_main_array_bottom()
def get_main_array_left(self):
return self.replica_bitcell_array.get_main_array_left()
def get_main_array_right(self):
return self.replica_bitcell_array.get_main_array_right()
def get_replica_top(self):
return self.replica_bitcell_array.get_replica_top()
def get_replica_bottom(self):
return self.replica_bitcell_array.get_replica_bottom()
def get_replica_left(self):
return self.replica_bitcell_array.get_replica_left()
def get_replica_right(self):
return self.replica_bitcell_array.get_replica_right()
def get_column_offsets(self):
return self.replica_bitcell_array.get_column_offsets()
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""
# Dynamic Power from Bitline
bl_wire = self.gen_bl_wire()
cell_load = 2 * bl_wire.return_input_cap()
bl_swing = OPTS.rbl_delay_percentage
freq = spice["default_event_frequency"]
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
# Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(corner, load)
# Leakage power grows with entire array and bitlines.
total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size,
cell_power.leakage * self.column_size * self.row_size)
return total_power
def gen_bl_wire(self):
if OPTS.netlist_only:
height = 0
else:
height = self.height
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size - bl_pos), height, drc("minwidth_m1"))
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire
def graph_exclude_bits(self, targ_row=None, targ_col=None):
"""
Excludes bits in column from being added to graph except target
"""
self.replica_bitcell_array.graph_exclude_bits(targ_row, targ_col)
def graph_exclude_replica_col_bits(self):
"""
Exclude all replica/dummy cells in the replica columns except the replica bit.
"""
self.replica_bitcell_array.graph_exclude_replica_col_bits()
def get_cell_name(self, inst_name, row, col):
"""
Gets the spice name of the target bitcell.
"""
return self.replica_bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.replica_bitcell_array_inst.name, row, col)
def clear_exclude_bits(self):
"""
Clears the bit exclusions
"""
self.replica_bitcell_array.clear_exclude_bits()

View File

@ -9,11 +9,11 @@ from math import sqrt
from openram import debug
from openram.base import vector
from openram.base import round_to_grid
from openram.modules import replica_bitcell_array
from openram.tech import drc
from openram.tech import array_row_multiple
from openram.tech import array_col_multiple
from openram import OPTS
from .replica_bitcell_array import replica_bitcell_array
from .sky130_bitcell_base_array import sky130_bitcell_base_array

View File

@ -43,6 +43,7 @@ tech_modules["bitcell_2port"] = "bitcell_2port"
tech_modules["bitcell_array"] = ["sky130_bitcell_array", "bitcell_array"]
tech_modules["replica_bitcell_array"] = ["sky130_replica_bitcell_array", "replica_bitcell_array"]
tech_modules["capped_replica_bitcell_array"] = ["sky130_capped_replica_bitcell_array", "capped_replica_bitcell_array"]
tech_modules["dummy_array"] = ["sky130_dummy_array", "dummy_array"]
tech_modules["replica_column"] = ["sky130_replica_column", "replica_column"]