Merge remote-tracking branch 'openram_local/array_gen' into merge/full-array-gen-into-dev

# Conflicts:
#	technology/sky130/custom/sky130_col_cap_array.py
This commit is contained in:
Jesse Cirimelli-Low 2026-04-30 12:43:19 -07:00
commit a5c879f510
65 changed files with 3289 additions and 3923 deletions

View File

@ -19,31 +19,30 @@ jobs:
run: |
rm -rf ~/.local/lib/python3.8/site-packages/openram*
make library
- name: Build conda
run: |
./install_conda.sh
- name: PDK Install
run: |
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology"
export PDK_ROOT="${{ github.workspace }}/pdk"
nix --extra-experimental-features 'nix-command flakes' develop --command bash -lc '
export OPENRAM_HOME="${{ github.workspace }}/compiler";
export OPENRAM_TECH="${{ github.workspace }}/technology";
export PDK_ROOT="${{ github.workspace }}/pdk";
# Add make targets to install PDKs of all technologies that need it
make sky130-pdk
make sky130-pdk;
make sky130-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"
nix --extra-experimental-features 'nix-command flakes' develop --command bash -lc '
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
unset KLAYOUT_PATH;
#cd $OPENRAM_HOME/.. && make pdk && make install
#export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp"
#python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm
#$OPENRAM_HOME/tests/regress.py -j 24 -t scn4m_subm
cd $OPENRAM_HOME/tests
make clean
cd $OPENRAM_HOME/tests;
make clean;
make -k -j 48
- name: Archive
if: ${{ failure() }}

1
.gitignore vendored
View File

@ -19,7 +19,6 @@ compiler/tests/results/
open_pdks/
dist/
openram.egg-info/
miniconda/
sky130A
sky130B
gf180mcuA

View File

@ -2,7 +2,6 @@ include Makefile
include openram.mk
include setpaths.sh
include requirements.txt
include install_conda.sh
include docker/*
recursive-include compiler *
recursive-include technology *

View File

@ -13,8 +13,7 @@ SRAM_LIB_GIT_REPO ?= https://github.com/vlsida/sky130_fd_bd_sram.git
# Use this for development
#SRAM_LIB_GIT_REPO ?= git@github.com:VLSIDA/sky130_fd_bd_sram.git
#SRAM_LIB_GIT_REPO ?= https://github.com/google/skywater-pdk-libs-sky130_fd_bd_sram.git
SRAM_LIB_GIT_COMMIT ?= dd64256961317205343a3fd446908b42bafba388
SRAM_LIB_GIT_COMMIT ?= fc63b12883b4bf458ee8c756ba64c37063e1ffb9
SKY130_PDK ?= $(PDK_ROOT)/sky130A
GF180_PDK ?= $(PDK_ROOT)/gf180mcuD
@ -58,8 +57,8 @@ 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 ciel from there
CONDA_DIR := $(wildcard $(TOP_DIR)/miniconda)
# If nix is available, run ciel via nix develop
NIX_BIN := $(shell command -v nix 2>/dev/null)
check-pdk-root:
ifndef PDK_ROOT
@ -103,23 +102,21 @@ sky130-install: $(SRAM_LIB_DIR)
sky130-pdk: $(SKY130_PDKS_DIR)
@echo "Installing SKY130 via ciel..."
ifeq ($(CONDA_DIR),)
ifeq ($(NIX_BIN),)
ciel enable --pdk sky130 $(SKY130_CIEL)
else
source $(TOP_DIR)/miniconda/bin/activate && \
ciel enable --pdk sky130 $(SKY130_CIEL) && \
conda deactivate
nix --extra-experimental-features 'nix-command flakes' develop --command \
ciel enable --pdk sky130 $(SKY130_CIEL)
endif
.PHONY: sky130-pdk
gf180mcu-pdk:
@echo "Installing GF180 via ciel..."
ifeq ($(CONDA_DIR),)
ifeq ($(NIX_BIN),)
ciel enable --pdk gf180mcu $(GF180_CIEL)
else
source $(TOP_DIR)/miniconda/bin/activate && \
ciel enable --pdk gf180mcu $(GF180_CIEL) && \
conda deactivate
nix --extra-experimental-features 'nix-command flakes' develop --command \
ciel enable --pdk gf180mcu $(GF180_CIEL)
endif
.PHONY: gf180mcu-pdk

View File

@ -23,22 +23,11 @@ if "OPENRAM_HOME" not in os.environ.keys():
__path__.insert(0, OPENRAM_HOME)
# 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")
# 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 conda setup directory.")
# Nix toolchain root (flake location)
NIX_HOME = os.path.abspath(OPENRAM_HOME + "/..")
if "NIX_HOME" in os.environ.keys():
NIX_HOME = os.environ["NIX_HOME"]
os.environ["NIX_HOME"] = NIX_HOME
# Import everything in globals.py

View File

@ -66,6 +66,8 @@ class contact(hierarchy_design):
self.offset = vector(0, 0)
self.implant_type = implant_type
self.well_type = well_type
# Module does not have pins, but has empty pin list.
self.pins = []
self.create_layout()
def create_layout(self):

View File

@ -164,7 +164,7 @@ class instance(geometry):
An instance of a module with a specified location, rotation,
spice pins, and spice nets
"""
def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0, is_bitcell=False):
"""Initializes an instance to represent a module"""
super().__init__()
debug.check(mirror not in ["R90", "R180", "R270"],
@ -176,6 +176,8 @@ class instance(geometry):
self.rotate = rotate
self.offset = vector(offset).snap_to_grid()
self.mirror = mirror
self.is_bitcell = is_bitcell
# track if the instance's spice pin connections have been made
self.connected = False
@ -183,10 +185,11 @@ class instance(geometry):
# change attributes in these spice objects
self.spice_pins = copy.deepcopy(self.mod.pins)
self.spice_nets = copy.deepcopy(self.mod.nets)
for pin in self.spice_pins.values():
pin.set_inst(self)
for net in self.spice_nets.values():
net.set_inst(self)
if "contact" not in mod.name:
for pin in self.spice_pins.values():
pin.set_inst(self)
for net in self.spice_nets.values():
net.set_inst(self)
if OPTS.netlist_only:
self.width = 0

View File

@ -9,6 +9,7 @@ import sys
import os
import re
from math import sqrt
from copy import deepcopy
from openram import debug
from openram.gdsMill import gdsMill
from openram import tech
@ -460,21 +461,32 @@ class layout():
for pin in pin_list:
pin.rect = [pin.ll() - offset, pin.ur() - offset]
def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0, is_bitcell=False):
""" Adds an instance of a mod to this module """
# Contacts are not really instances, so skip them
if "contact" not in mod.name:
# Check that the instance name is unique
debug.check(name not in self.inst_names, "Duplicate named instance in {0}: {1}".format(self.cell_name, name))
self.mods.add(mod)
self.inst_names.add(name)
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate, is_bitcell))
debug.info(3, "adding instance {}".format(self.insts[-1]))
# This is commented out for runtime reasons
# debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
return self.insts[-1]
def add_existing_inst(self, inst, name):
#new_inst = deepcopy(inst)
new_inst = self.add_inst(name, inst.mod, offset=inst.offset, mirror=inst.mirror, rotate=inst.rotate, is_bitcell=inst.is_bitcell)
#new_inst.mod = inst.mod
self.mods.add(new_inst.mod)
#if name:
# new_inst.name = name
self.inst_names.add(new_inst.name)
#self.insts.append(new_inst)
debug.info(3, "adding existing instance{}".format(self.insts[-1]))
return self.insts[-1]
def get_inst(self, name):
""" Retrieve an instance by name """
for inst in self.insts:
@ -2149,6 +2161,13 @@ class layout():
# Add the gnd ring
self.add_ring([ll, ur])
def reset_coordinates(self):
ll=vector(min([x.lx() for x in self.insts]),min([y.by() for y in self.insts]))
self.translate_all(ll)
self.width = max([x.rx() for x in self.insts]) - min([x.lx() for x in self.insts])
self.height = max([x.uy() for x in self.insts]) - min([y.by() for y in self.insts])
def add_ring(self, bbox=None, width_mult=8, offset=0):
"""
Add a ring around the bbox
@ -2201,7 +2220,7 @@ class layout():
size=(supply_vias,
supply_vias))
def add_power_ring(self):
def add_power_ring(self, h_layer="m2", v_layer="m1", top=True, bottom=True, left=True, right=True):
"""
Create vdd and gnd power rings around an area of the bounding box
argument. Must have a supply_rail_width and supply_rail_pitch
@ -2209,118 +2228,120 @@ class layout():
left/right/top/bottom vdd/gnd center offsets for use in other
modules..
"""
[ll, ur] = self.bbox
supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
supply_rail_spacing = self.supply_rail_pitch
height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing
width = (ur.x - ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing
# LEFT vertical rails
offset = ll + vector(-2 * self.supply_rail_pitch,
-2 * self.supply_rail_pitch)
left_gnd_pin = self.add_layout_pin(text="gnd",
layer="m2",
offset=offset,
width=self.supply_rail_width,
height=height)
if left:
offset = ll + vector(-2*self.supply_rail_pitch,
-2*self.supply_rail_pitch)
self.left_gnd_pin = self.add_layout_pin(text="gnd",
layer=v_layer,
offset=offset,
width=self.supply_rail_width,
height=height + 2 * supply_rail_spacing)
offset = ll + vector(-1 * self.supply_rail_pitch,
-1 * self.supply_rail_pitch)
left_vdd_pin = self.add_layout_pin(text="vdd",
layer="m2",
offset=offset,
width=self.supply_rail_width,
height=height)
offset = ll + vector(-1 * self.supply_rail_pitch,
-1 * self.supply_rail_pitch)
self.left_vdd_pin = self.add_layout_pin(text="vdd",
layer=v_layer,
offset=offset,
width=self.supply_rail_width,
height=height)
# RIGHT vertical rails
offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch)
right_gnd_pin = self.add_layout_pin(text="gnd",
layer="m2",
offset=offset,
width=self.supply_rail_width,
height=height)
if right:
# RIGHT vertical railsteac a 460
offset = vector(ur.x, ll.y) + vector(2 * self.supply_rail_pitch - self.supply_rail_width,
-2 * self.supply_rail_pitch)
self.right_gnd_pin = self.add_layout_pin(text="gnd",
layer=v_layer,
offset=offset,
width=self.supply_rail_width,
height=height + 2* supply_rail_spacing)
offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch,
-1 * self.supply_rail_pitch)
right_vdd_pin = self.add_layout_pin(text="vdd",
layer="m2",
offset=offset,
width=self.supply_rail_width,
height=height)
offset = vector(ur.x, ll.y) + vector(1 * self.supply_rail_pitch - self.supply_rail_width,
-1 * self.supply_rail_pitch)
self.right_vdd_pin = self.add_layout_pin(text="vdd",
layer=v_layer,
offset=offset,
width=self.supply_rail_width,
height=height)
# BOTTOM horizontal rails
offset = ll + vector(-2 * self.supply_rail_pitch,
-2 * self.supply_rail_pitch)
bottom_gnd_pin = self.add_layout_pin(text="gnd",
layer="m1",
offset=offset,
width=width,
height=self.supply_rail_width)
if bottom:
# BOTTOM horizontal rails
offset = ll + vector(-2 * self.supply_rail_pitch,
-2 * self.supply_rail_pitch)
self.bottom_gnd_pin = self.add_layout_pin(text="gnd",
layer=h_layer,
offset=offset,
width=width + 2 * supply_rail_spacing,
height=self.supply_rail_width)
offset = ll + vector(-1 * self.supply_rail_pitch,
-1 * self.supply_rail_pitch)
bottom_vdd_pin = self.add_layout_pin(text="vdd",
layer="m1",
offset=offset,
width=width,
height=self.supply_rail_width)
offset = ll + vector(-1 * self.supply_rail_pitch,
-1 * self.supply_rail_pitch)
self.bottom_vdd_pin = self.add_layout_pin(text="vdd",
layer=h_layer,
offset=offset,
width=width,
height=self.supply_rail_width)
if top:
# TOP horizontal rails
offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch,
2 * self.supply_rail_pitch - self.supply_rail_width)
self.top_gnd_pin = self.add_layout_pin(text="gnd",
layer=h_layer,
offset=offset,
width=width + 2 * supply_rail_spacing,
height=self.supply_rail_width)
# TOP horizontal rails
offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch,
0)
top_gnd_pin = self.add_layout_pin(text="gnd",
layer="m1",
offset=offset,
width=width,
height=self.supply_rail_width)
offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch,
self.supply_rail_pitch)
top_vdd_pin = self.add_layout_pin(text="vdd",
layer="m1",
offset=offset,
width=width,
height=self.supply_rail_width)
offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch,
1 * self.supply_rail_pitch - self.supply_rail_width)
self.top_vdd_pin = self.add_layout_pin(text="vdd",
layer=h_layer,
offset=offset,
width=width,
height=self.supply_rail_width)
# Remember these for connecting things in the design
self.left_gnd_x_center = left_gnd_pin.cx()
self.left_vdd_x_center = left_vdd_pin.cx()
self.right_gnd_x_center = right_gnd_pin.cx()
self.right_vdd_x_center = right_vdd_pin.cx()
self.bottom_gnd_y_center = bottom_gnd_pin.cy()
self.bottom_vdd_y_center = bottom_vdd_pin.cy()
self.top_gnd_y_center = top_gnd_pin.cy()
self.top_vdd_y_center = top_vdd_pin.cy()
# Find the number of vias for this pitch
self.supply_vias = 1
while True:
c = factory.create(module_type="contact",
layer_stack=self.m1_stack,
dimensions=(self.supply_vias, self.supply_vias))
if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width:
self.supply_vias += 1
else:
self.supply_vias -= 1
break
via_points = [vector(self.left_gnd_x_center, self.bottom_gnd_y_center),
vector(self.left_gnd_x_center, self.top_gnd_y_center),
vector(self.right_gnd_x_center, self.bottom_gnd_y_center),
vector(self.right_gnd_x_center, self.top_gnd_y_center),
vector(self.left_vdd_x_center, self.bottom_vdd_y_center),
vector(self.left_vdd_x_center, self.top_vdd_y_center),
vector(self.right_vdd_x_center, self.bottom_vdd_y_center),
vector(self.right_vdd_x_center, self.top_vdd_y_center)]
if left:
self.left_gnd_x_center = self.left_gnd_pin.cx()
self.left_vdd_x_center = self.left_vdd_pin.cx()
if right:
self.right_gnd_x_center = self.right_gnd_pin.cx()
self.right_vdd_x_center = self.right_vdd_pin.cx()
if bottom:
self.bottom_gnd_y_center = self.bottom_gnd_pin.cy()
self.bottom_vdd_y_center = self.bottom_vdd_pin.cy()
if top:
self.top_gnd_y_center = self.top_gnd_pin.cy()
self.top_vdd_y_center = self.top_vdd_pin.cy()
via_points = []
if left and bottom:
via_points.append((self.left_gnd_x_center, self.bottom_gnd_y_center))
if left and top:
via_points.append(vector(self.left_gnd_x_center, self.top_gnd_y_center))
if right and bottom:
via_points.append(vector(self.right_gnd_x_center, self.bottom_gnd_y_center))
if right and top:
via_points.append(vector(self.right_gnd_x_center, self.top_gnd_y_center))
if left and bottom:
via_points.append(vector(self.left_vdd_x_center, self.bottom_vdd_y_center))
if left and top:
via_points.append(vector(self.left_vdd_x_center, self.top_vdd_y_center))
if right and bottom:
via_points.append(vector(self.right_vdd_x_center, self.bottom_vdd_y_center))
if right and top:
via_points.append((self.right_vdd_x_center, self.top_vdd_y_center))
for pt in via_points:
self.add_via_center(layers=self.m1_stack,
offset=pt,
size=(self.supply_vias,
self.supply_vias))
self.add_via_stack_center(offset=pt,
from_layer=h_layer,
to_layer=v_layer,
min_area=True)
def pdf_write(self, pdf_name):
"""
Display the layout to a PDF file.

View File

@ -50,6 +50,7 @@ class spice():
if not os.path.exists(self.lvs_file):
self.lvs_file = self.sp_file
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"]
# Holds subckts/mods for this module
self.mods = set()
# Holds the pins for this module (in order)
@ -699,7 +700,8 @@ class spice():
def get_instance_connections(self):
conns = []
for inst in self.insts:
conns.append(inst.get_connections())
if "contact" not in inst.name:
conns.append(inst.get_connections())
return conns
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):

View File

@ -619,10 +619,10 @@ class simulation():
Gets the signal name associated with the bitlines in the bank.
"""
# FIXME: change to a solution that does not depend on the technology
if OPTS.tech_name == "sky130" and len(self.all_ports) == 1:
cell_mod = factory.create(module_type=OPTS.bitcell, version="opt1")
else:
cell_mod = factory.create(module_type=OPTS.bitcell)
#if OPTS.tech_name == "sky130" and len(self.all_ports) == 1:
# cell_mod = factory.create(module_type=OPTS.bitcell, version="opt1")
#else:
cell_mod = factory.create(module_type=OPTS.bitcell)
cell_bl = cell_mod.get_bl_name(port)
cell_br = cell_mod.get_br_name(port)

View File

@ -12,6 +12,7 @@ simulations as well.
"""
import os
import shlex
import subprocess
import numpy as np
from openram import debug
@ -406,11 +407,15 @@ class stimuli():
spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w')
spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w')
# Wrap the command with conda activate & conda deactivate
# Run spice in the Nix devShell when Nix-managed tools are enabled.
# FIXME: Should use verify/run_script.py here but run_script doesn't return
# the return code of the subprocess. File names might also mismatch.
from openram import CONDA_HOME
cmd = "/bin/bash -c 'source {0}/bin/activate && {1} && conda deactivate'".format(CONDA_HOME, cmd)
if OPTS.use_nix:
cmd = (
"nix --extra-experimental-features 'nix-command flakes' "
"develop --command /bin/bash -lc {0}"
.format(shlex.quote(cmd))
)
debug.info(2, cmd)
proc = subprocess.run(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)

View File

@ -144,11 +144,11 @@ class _pgate:
class bitcell(cell):
def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False):
def __init__(self, port_order, port_types, port_map=None, storage_nets=["Q", "Q_bar"], mirror=None, end_caps=False, has_corners=True):
super().__init__(port_order, port_types, port_map)
self.end_caps = end_caps
self.has_corners = has_corners
if not mirror:
self.mirror = _mirror_axis(True, False)
else:

View File

@ -67,11 +67,14 @@ def parse_args():
dest="num_sim_threads"),
optparse.make_option("-v", "--verbose",
action="count",
dest="verbose_level",
dest="5",
help="Increase the verbosity level"),
optparse.make_option("-t", "--tech",
dest="tech_name",
help="Technology name"),
optparse.make_option("-f", "--tech_file",
dest="tech_file",
help="Technology name"),
optparse.make_option("-s", "--spice",
dest="spice_name",
help="Spice simulator executable name"),
@ -185,7 +188,7 @@ def init_openram(config_file, is_unit_test=False):
read_config(config_file, is_unit_test)
install_conda()
install_nix()
import_tech()
@ -206,17 +209,40 @@ def init_openram(config_file, is_unit_test=False):
from openram import verify
def install_conda():
""" Setup conda for default tools. """
def install_nix():
"""Initialize Nix-based toolchain dependencies."""
# Don't setup conda if not used
if not OPTS.use_conda or OPTS.is_unit_test:
# Don't setup tools during unit tests.
if OPTS.is_unit_test:
return
debug.info(1, "Creating conda setup...");
if not OPTS.use_nix or OPTS.is_unit_test:
return
from openram import CONDA_INSTALLER
subprocess.call(CONDA_INSTALLER)
debug.info(1, "Bootstrapping toolchain with Nix...")
nix_exe = shutil.which("nix")
if nix_exe is None:
debug.error("Nix is required for automatic tool setup, but 'nix' was not found in PATH.", -1)
repo_root = os.path.abspath(os.path.join(OPENRAM_HOME, ".."))
flake_file = os.path.join(repo_root, "flake.nix")
if not os.path.exists(flake_file):
debug.error("Expected Nix flake at {} for tool setup.".format(flake_file), -1)
# Trigger materialization/build of the devShell dependencies once.
# Environment activation still happens outside OpenRAM via `nix develop`.
cmd = [
nix_exe,
"--extra-experimental-features", "nix-command flakes",
"develop",
"--command", "true",
]
result = subprocess.call(cmd, cwd=repo_root)
if result != 0:
debug.error("Failed to initialize Nix toolchain (nix develop returned {}).".format(result), -1)
return
def setup_bitcell():
@ -443,14 +469,7 @@ def find_exe(check_exe):
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
search_path = "{0}/bin{1}{2}".format(CONDA_HOME,
os.pathsep,
os.environ["PATH"])
else:
search_path = os.environ["PATH"]
search_path = os.environ["PATH"]
# Check if the preferred spice option exists in the path
for path in search_path.split(os.pathsep):

View File

@ -41,7 +41,7 @@ from .local_bitcell_array import *
from .nand2_dec import *
from .nand3_dec import *
from .nand4_dec import *
from .orig_bitcell_array import *
#from .orig_bitcell_array import *
from .pand2 import *
from .pand3 import *
from .pand4 import *

View File

@ -10,15 +10,17 @@ from openram.tech import drc, spice
from openram.sram_factory import factory
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
from .pattern import pattern
from openram.base import geometry, instance
from math import ceil
class bitcell_array(bitcell_base_array):
"""
Creates a rows x cols array of memory cells.
Assumes bit-lines and word lines are connected by abutment.
"""
def __init__(self, rows, cols, column_offset=0, name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name)
def __init__(self, rows, cols, column_offset=0, row_offset=0, name="", left_rbl=None, right_rbl=None):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.add_comment("rows: {0} cols: {1}".format(rows, cols))
@ -42,7 +44,7 @@ class bitcell_array(bitcell_base_array):
def create_layout(self):
self.place_array("bit_r{0}_c{1}")
self.place_array()
self.add_layout_pins()
@ -57,18 +59,29 @@ class bitcell_array(bitcell_base_array):
self.cell = factory.create(module_type=OPTS.bitcell)
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.cell)
self.connect_inst(self.get_bitcell_pins(row, col))
r = self.row_offset
c = self.column_offset
self.cell_inst={}
if self.cell.mirror.y:
core_block = [[0 for x in range(2)] for y in range(2)]
core_block[(0 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(0+c)%2}", mod=self.cell, is_bitcell=True, mirror='')
core_block[(0 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(1+c)%2}", mod=self.cell, is_bitcell=True, mirror='MY')
core_block[(1 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(0+c)%2}", mod=self.cell, is_bitcell=True, mirror='MX')
core_block[(1 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(1+c)%2}", mod=self.cell, is_bitcell=True, mirror='XY')
else:
core_block = [[0 for x in range(1)] for y in range(2)]
core_block[(0 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_0_0", mod=self.cell, is_bitcell=True)
core_block[(1 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_1_0", mod=self.cell, is_bitcell=True, mirror='MX')
#print(r, c)
#print(core_block)
# If it is a "core" cell, it could be trimmed for sim time
if col>0 and col<self.column_size-1 and row>0 and row<self.row_size-1:
self.trim_insts.add(name)
self.pattern = pattern(self, "bitcell_array", core_block, num_rows=self.row_size, num_cols=self.column_size,name_template="bit_r{0}_c{1}")
self.pattern.connect_array()
for key in self.cell_inst.keys():
(row, col) = key
if col>0 and col<self.column_size-1 and row>0 and row<self.row_size-1:
self.trim_insts.add(self.cell_inst[key].name)
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""

View File

@ -25,6 +25,7 @@ class bitcell_base(design):
self.nets_match = self.do_nets_exist(prop.storage_nets)
self.mirror = prop.mirror
self.end_caps = prop.end_caps
self.has_corners = prop.has_corners
def get_stage_effort(self, load):
parasitic_delay = 1
# This accounts for bitline being drained

View File

@ -6,23 +6,24 @@
# All rights reserved.
#
from openram import debug
from openram.base.geometry import instance
from openram.base import design
from openram.sram_factory import factory
from openram import OPTS
from openram.modules import pattern
class bitcell_base_array(design):
"""
Abstract base class for bitcell-arrays -- bitcell, dummy, replica
"""
def __init__(self, name, rows, cols, column_offset):
def __init__(self, name, rows, cols, column_offset, row_offset):
super().__init__(name)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.column_size = cols
self.row_size = rows
self.column_offset = column_offset
self.row_offset = row_offset
# Bitcell for port names only
self.cell = factory.create(module_type=OPTS.bitcell)
@ -120,13 +121,16 @@ class bitcell_base_array(design):
def get_all_wordline_names(self, port=None):
""" Return all the wordline names """
temp = []
temp.extend(self.get_rbl_wordline_names(0))
if len(self.all_ports) > 0:
temp.extend(self.get_rbl_wordline_names(0))
if port == None:
temp.extend(self.all_wordline_names)
else:
temp.extend(self.wordline_names[port])
if len(self.all_ports) > 1:
temp.extend(self.get_rbl_wordline_names(1))
return temp
def add_bitline_pins(self):
@ -157,14 +161,54 @@ class bitcell_base_array(design):
height=wl_pin.height())
def route_supplies(self):
for inst in self.cell_inst.values():
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
vdd_found = False
gnd_found = False
for inst in self.insts:
if 'vdd' in inst.mod.get_pin_names():
vdd_found = True
if 'gnd' in inst.mod.get_pin_names():
gnd_found = True
power_name = 'vdd'
ground_name = 'gnd'
if vdd_found == False or gnd_found == False:
for inst in self.insts:
if 'VDD' in inst.mod.get_pin_names():
vdd_found = True
power_name = 'VDD'
if 'GND' in inst.mod.get_pin_names():
gnd_found = True
ground_name = 'GND'
if vdd_found is False or gnd_found is False:
from openram.tech import cell_properties
try:
power_name = cell_properties.power_name
except:
pass
try:
ground_name = cell_properties.ground_name
except:
pass
for inst in self.insts:
if power_name in inst.mod.get_pin_names():
self.copy_layout_pin(inst, power_name, new_name='vdd')
if ground_name in inst.mod.get_pin_names():
self.copy_layout_pin(inst, ground_name , new_name='gnd')
def add_layout_pins(self):
""" Add the layout pins """
self.add_bitline_pins()
self.add_wl_pins()
if self.get_bitline_names():
self.add_bitline_pins()
if self.get_wordline_names():
self.add_wl_pins()
def _adjust_x_offset(self, xoffset, col, col_offset):
tempx = xoffset
@ -184,32 +228,8 @@ class bitcell_base_array(design):
dir_x = True
return (tempy, dir_x)
def place_array(self, name_template, row_offset=0):
# We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size * self.cell.height
self.width = self.column_size * self.cell.width
xoffset = 0.0
for col in range(self.column_size):
yoffset = 0.0
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
for row in range(self.row_size):
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
if dir_x and dir_y:
dir_key = "XY"
elif dir_x:
dir_key = "MX"
elif dir_y:
dir_key = "MY"
else:
dir_key = ""
self.cell_inst[row, col].place(offset=[tempx, tempy],
mirror=dir_key)
yoffset += self.cell.height
xoffset += self.cell.width
def place_array(self):
self.pattern.place_array()
def get_column_offsets(self):
"""

View File

@ -20,7 +20,7 @@ class capped_replica_bitcell_array(bitcell_base_array):
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)
super().__init__(name, rows, cols, column_offset=0, row_offset=0)
debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name,
rows,
cols,
@ -51,10 +51,10 @@ class capped_replica_bitcell_array(bitcell_base_array):
self.rbls = self.left_rbl + self.right_rbl
# Two dummy rows plus replica even if we don't add the column
self.extra_rows = sum(self.rbl)
self.extra_rows = sum(self.rbl) + 2
# 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
#if not self.cell.end_caps:
# self.extra_rows += 2
self.create_netlist()
if not OPTS.netlist_only:
@ -73,6 +73,8 @@ class capped_replica_bitcell_array(bitcell_base_array):
cols=self.column_size,
rows=self.row_size,
rbl=self.rbl,
column_offset=1,
row_offset=1,
left_rbl=self.left_rbl,
right_rbl=self.right_rbl)
@ -86,35 +88,39 @@ class capped_replica_bitcell_array(bitcell_base_array):
rows=1,
# dummy column + left replica column(s)
column_offset=1,
row_offset=self.row_size+ self.extra_rows + 1, #add 1 to account for bottom col_cap
mirror=0,
location="top")
location="top",
left_rbl=self.left_rbl,
right_rbl=self.right_rbl)
self.col_cap_bottom = factory.create(module_type=col_cap_module_type,
cols=self.column_size + len(self.rbls),
rows=1,
# dummy column + left replica column(s)
column_offset=1,
mirror=0,
location="bottom")
row_offset=0,
mirror=(1+self.row_size+self.extra_rows) % 2,
location="bottom",
left_rbl=self.left_rbl,
right_rbl=self.right_rbl)
# 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)
column_offset=0,
row_offset=0,
location="left")
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)
column_offset=1 + len(self.left_rbl) + self.column_size + len(self.right_rbl),
row_offset=0,
location="right")
def add_pins(self):
@ -158,10 +164,21 @@ class capped_replica_bitcell_array(bitcell_base_array):
self.unused_wordline_names = self.replica_bitcell_array.unused_wordline_names
self.replica_array_wordline_names_with_grounded_wls = ["gnd" if x in self.unused_wordline_names else x for x in self.replica_bitcell_array.wordline_pin_list]
# Left/right row caps cover the full array height. Pad with gnd so the
# netlist list length matches the row cap (replica in the center); do
# not use col cap wordline heuristics.
n_rowcap_wl = len(self.row_cap_left.get_wordline_names())
n_rba_wl = len(self.replica_array_wordline_names_with_grounded_wls)
self.wordline_pin_list = []
self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_top.get_wordline_names()))
if self.rbls:
self.wordline_pin_list.extend(["gnd"] * len(self.rbls))
self.wordline_pin_list.extend(self.replica_array_wordline_names_with_grounded_wls)
self.wordline_pin_list.extend(["gnd"] * len(self.col_cap_bottom.get_wordline_names()))
if self.rbls:
self.wordline_pin_list.extend(["gnd"] * len(self.rbls))
self.add_pin_list(self.used_wordline_names, "INPUT")
@ -177,7 +194,7 @@ class capped_replica_bitcell_array(bitcell_base_array):
# 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))
mod=self.col_cap_bottom,))
self.connect_inst(self.bitline_pin_list + ["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))
@ -188,6 +205,10 @@ class capped_replica_bitcell_array(bitcell_base_array):
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.wordline_pin_list + self.supplies)
#print(self.dummy_col_insts[0].mod.pins)
#print(["dummy_left_" + bl for bl in self.row_cap_left.all_bitline_names] + self.wordline_pin_list + 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.wordline_pin_list + self.supplies)
@ -201,40 +222,34 @@ class capped_replica_bitcell_array(bitcell_base_array):
# 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]))
# FIXME: custom sky130 replica module has a better version of this offset
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 replica array
self.replica_bitcell_array_inst.place(offset=self.unused_offset)
self.replica_bitcell_array_inst.place(offset=0)
self.add_end_caps()
# shift everything up and right to account for cap cells
self.translate_all(self.bitcell_offset.scale(-1, -1))
self.width = self.dummy_col_insts[1].rx() + self.unused_offset.x
self.height = self.dummy_row_insts[1].uy()
self.add_layout_pins()
ll = vector(-1 * self.dummy_col_insts[0].width, -1 * self.dummy_row_insts[0].height)
self.translate_all(ll)
self.capped_rba_width = self.dummy_col_insts[0].width + self.dummy_row_insts[0].width + self.dummy_col_insts[1].width
self.capped_rba_height = self.dummy_col_insts[0].height
self.route_power_ring(self.supply_stack[2], self.supply_stack[0])
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.reset_coordinates()
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def route_power_ring(self, v_layer, h_layer):
self.bbox = (vector(0,0), vector(self.capped_rba_width, self.capped_rba_height))
self.supply_rail_width = drc["minwidth_m3"]
self.supply_rail_pitch = 6 * self.supply_rail_width
self.add_power_ring(v_layer=v_layer, h_layer=h_layer)
def get_main_array_top(self):
return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_top()
@ -248,7 +263,7 @@ class capped_replica_bitcell_array(bitcell_base_array):
def get_main_array_right(self):
return self.replica_bitcell_array_inst.lx() + self.replica_bitcell_array.get_main_array_right()
# FIXME: these names need to be changed to reflect what they're actually returning
#FIXME: these names need to be changed to reflect what they're actually returning
def get_replica_top(self):
return self.dummy_row_insts[1].by()
@ -261,7 +276,6 @@ class capped_replica_bitcell_array(bitcell_base_array):
def get_replica_right(self):
return self.dummy_col_insts[1].rx()
def get_column_offsets(self):
"""
Return an array of the x offsets of all the regular bits
@ -273,47 +287,50 @@ class capped_replica_bitcell_array(bitcell_base_array):
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, flip_dummy) + self.replica_bitcell_array_inst.ul()
self.dummy_row_insts[1].place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# Far top dummy row
offset = self.replica_bitcell_array_inst.ul()
self.dummy_row_insts[1].place(offset=offset)
# 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, flip_dummy - 1) + self.unused_offset
self.dummy_row_insts[0].place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0")
# Far bottom dummy row
dummy_row_height = vector(0, self.dummy_row_insts[0].height)
offset = self.replica_bitcell_array_inst.ll() - dummy_row_height
self.dummy_row_insts[0].place(offset=offset)
# 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(-1, -1) + self.unused_offset
self.dummy_col_insts[0].place(offset=dummy_col_offset)
dummy_col_width = vector(self.dummy_col_insts[0].width, 0)
offset = self.dummy_row_insts[0].ll() - dummy_col_width
if self.dummy_col_insts[0].mod.cell.has_corners is False:
offset += vector(0, dummy_row_height.y)
self.dummy_col_insts[0].place(offset=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(0, -1) + self.replica_bitcell_array_inst.lr()
self.dummy_col_insts[1].place(offset=dummy_col_offset)
offset = self.dummy_row_insts[0].lr()
if self.dummy_col_insts[0].mod.cell.has_corners is False:
offset += vector(0, dummy_row_height.y)
self.dummy_col_insts[1].place(offset=offset)
def add_layout_pins(self):
for pin_name in self.used_wordline_names + self.bitline_pin_list:
pin = self.replica_bitcell_array_inst.get_pin(pin_name)
if "wl" in pin_name:
# wordlines
pin_offset = pin.ll().scale(0, 1)
pin_width = self.width
pin_width = self.capped_rba_width
pin_height = pin.height()
else:
# bitlines
pin_offset = pin.ll().scale(1, 0)
pin_width = pin.width()
pin_height = self.height
pin_height = self.capped_rba_height
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin_offset,
width=pin_width,
height=pin_height)
def route_supplies(self):
@ -321,72 +338,100 @@ class capped_replica_bitcell_array(bitcell_base_array):
bitcell = factory.create(module_type="pbitcell")
else:
bitcell = getattr(props, "bitcell_{}port".format(OPTS.num_ports))
top = True
bottom = True
left = False
right = False
if top:
inst = self.dummy_row_insts[1]
if 'vdd' in inst.mod.pins:
array_pins = inst.get_pins('vdd')
for array_pin in array_pins:
supply_pin = self.top_vdd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(array_pin.center()[0], supply_pin.center()[1]))
vdd_dir = bitcell.vdd_dir
gnd_dir = bitcell.gnd_dir
array_pins = inst.get_pins('gnd')
for array_pin in array_pins:
supply_pin = self.top_gnd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(array_pin.center()[0], supply_pin.center()[1]))
if bottom:
inst = self.dummy_row_insts[0]
if 'vdd' in inst.mod.pins:
array_pins = inst.get_pins('vdd')
for array_pin in array_pins:
supply_pin = self.bottom_vdd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(array_pin.center()[0], supply_pin.center()[1]))
# vdd/gnd are only connected in the perimeter cells
supply_insts = self.dummy_col_insts + self.dummy_row_insts
array_pins = inst.get_pins('gnd')
for array_pin in array_pins:
supply_pin = self.bottom_gnd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(array_pin.center()[0], supply_pin.center()[1]))
if left:
inst = self.dummy_col_insts[0]
if 'vnd' in inst.mod.pins:
array_pins = inst.get_pins('vnd')
for array_pin in array_pins:
supply_pin = self.left_vdd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(supply_pin.center()[0], array_pin.center()[1]))
# 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)
array_pins = inst.get_pins('gnd')
for array_pin in array_pins:
supply_pin = self.left_gnd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(supply_pin.center()[0], array_pin.center()[1]))
if right:
inst = self.dummy_col_insts[1]
if 'vdd' in inst.mod.pins:
array_pins = inst.get_pins('vdd')
for array_pin in array_pins:
supply_pin = self.right_vdd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(supply_pin.center()[0], array_pin.center()[1]))
array_pins = inst.get_pins('gnd')
for array_pin in array_pins:
supply_pin = self.right_gnd_pin
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
self.add_via_stack_center(from_layer = array_pin.layer,
to_layer = supply_pin.layer,
offset = vector(supply_pin.center()[0], array_pin.center()[1]))
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():
for wl_name in inst.mod.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)
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx())
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx())
# Ground the unused replica wordlines
for wl_name in self.unused_wordline_names:
pin = self.replica_bitcell_array_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)
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx())
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx())
def route_side_pin(self, name, side, offset_multiple=1):
"""

View File

@ -6,14 +6,16 @@
from openram.sram_factory import factory
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
from openram.base import geometry
from .pattern import pattern
from math import ceil
class col_cap_array(bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name)
def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name="",left_rbl=[],right_rbl=[]):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name)
self.mirror = mirror
self.location = location
@ -37,28 +39,44 @@ class col_cap_array(bitcell_base_array):
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.place_array()
self.add_layout_pins()
self.height = self.dummy_cell.height
self.width = self.column_size * self.cell.width
#self.height = self.dummy_cell.height
#self.width = self.column_size * self.cell.width
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
""" Add the modules used in this design """
self.dummy_cell = factory.create(module_type="col_cap_{}".format(OPTS.bitcell))
self.colend = factory.create(module_type="col_cap_{}".format(OPTS.bitcell))
# def create_instances(self):
# """ Create the module instances used in this design """
# self.cell_inst = {}
# for col in range(self.column_size):
# for row in range(self.row_size):
# name = "bit_r{0}_c{1}".format(row, col)
# self.cell_inst[row, col]=self.add_inst(name=name,
# mod=self.dummy_cell)
# self.connect_inst(self.get_bitcell_pins(row, col))
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(row, col))
self.cell_inst={}
if self.row_offset % 2 == 0:
bit_row = [geometry.instance("00_colend", mod=self.colend, is_bitcell=True, mirror="MY")]\
+ [geometry.instance("01_colend", mod=self.colend, is_bitcell=True)]
else:
bit_row = [geometry.instance("00_colend", mod=self.colend, is_bitcell=True, mirror="XY")]\
+ [geometry.instance("01_colend", mod=self.colend, is_bitcell=True, mirror="MX")]
bit_row = pattern.rotate_list(bit_row, self.column_offset * 2)
bit_block = []
pattern.append_row_to_block(bit_block, bit_row)
self.pattern = pattern(self, "col_cap_array_" + self.location , bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="col_cap_array" + self.location + "_r{0}_c{1}")
self.pattern.connect_array()
def get_bitcell_pins(self, row, col):
"""
@ -79,24 +97,24 @@ class col_cap_array(bitcell_base_array):
return bitcell_pins
def add_layout_pins(self):
""" Add the layout pins """
# def add_layout_pins(self):
# """ Add the layout pins """
column_list = self.cell.get_all_bitline_names()
# column_list = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for cell_column in column_list:
bl_pin = self.cell_inst[0, col].get_pin(cell_column)
self.add_layout_pin(text=cell_column + "_{0}".format(col),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
# for col in range(self.column_size):
# for cell_column in column_list:
# bl_pin = self.cell_inst[0, col].get_pin(cell_column)
# self.add_layout_pin(text=cell_column + "_{0}".format(col),
# layer=bl_pin.layer,
# offset=bl_pin.ll().scale(1, 0),
# width=bl_pin.width(),
# height=self.height)
# Add vdd/gnd via stacks
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.copy_layout_pin(inst, pin_name)
# # Add vdd/gnd via stacks
# for row in range(self.row_size):
# for col in range(self.column_size):
# inst = self.cell_inst[row, col]
# for pin_name in ["vdd", "gnd"]:
# for pin in inst.get_pins(pin_name):
# self.copy_layout_pin(inst, pin_name)

View File

@ -6,16 +6,20 @@
from openram.sram_factory import factory
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
from openram.base import geometry
from .pattern import pattern
class dummy_array(bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, column_offset=0, mirror=0, location="", name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name)
self.mirror = mirror
def __init__(self, rows, cols, column_offset=0, row_offset=0 ,mirror=0, location="", name="", left_rbl=0, right_rbl=0):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name)
self.location = location
self.row_offset = row_offset
self.mirror = mirror
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -32,7 +36,7 @@ class dummy_array(bitcell_base_array):
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.place_array()
self.add_layout_pins()
@ -50,13 +54,24 @@ class dummy_array(bitcell_base_array):
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(row, col))
self.cell_inst={}
r = self.row_offset
c = self.column_offset
if self.cell.mirror.y:
core_block = [[0 for x in range(2)] for y in range(2)]
core_block[(0 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(0+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='')
core_block[(0 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(0 + r)%2}_{(1+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='MY')
core_block[(1 + r) % 2][(0+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(0+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='MX')
core_block[(1 + r) % 2][(1+c) %2] = geometry.instance(f"core_{(1 + r)%2}_{(1+c)%2}", mod=self.dummy_cell, is_bitcell=True, mirror='XY')
else:
core_block = [[0 for x in range(1)] for y in range(2)]
core_block[(0 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_0_0", mod=self.dummy_cell, is_bitcell=True)
core_block[(1 + self.row_offset) % 2][(0+self.column_offset) %2] = geometry.instance("core_1_0", mod=self.dummy_cell, is_bitcell=True, mirror='MX')
#print(r, c)
#print(core_block)
self.pattern = pattern(self, "dummy_array", core_block, num_rows=self.row_size, num_cols=self.column_size, name_template="bit_r{0}_c{1}")
self.pattern.connect_array()
def add_pins(self):
# bitline pins are not added because they are floating
@ -68,47 +83,6 @@ class dummy_array(bitcell_base_array):
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def add_layout_pins(self):
""" Add the layout pins """
# Add the bitline metal, but not as pins since they are going to just be floating
# For some reason, LVS has an issue if we don't add this metal
bitline_names = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for port in self.all_ports:
bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port])
self.add_layout_pin(text="bl_{0}_{1}".format(port, col),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1])
self.add_layout_pin(text="br_{0}_{1}".format(port, col),
layer=br_pin.layer,
offset=br_pin.ll().scale(1, 0),
width=br_pin.width(),
height=self.height)
wl_names = self.cell.get_all_wl_names()
for row in range(self.row_size):
for port in self.all_ports:
wl_pins = self.cell_inst[row, 0].get_pins(wl_names[port])
for wl_pin in wl_pins:
self.add_layout_pin(text="wl_{0}_{1}".format(port, row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
def route_supplies(self):
# Copy a vdd/gnd layout pin from every cell
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
def input_load(self):
# FIXME: This appears to be old code from previous characterization. Needs to be updated.
wl_wire = self.gen_wl_wire()

View File

@ -1,116 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2024 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from openram.sram_factory import factory
from openram.tech import drc, spice
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
class bitcell_array(bitcell_base_array):
"""
Creates a rows x cols array of memory cells. Assumes bit-lines
and word line is connected by abutment.
Connects the word lines and bit lines.
"""
def __init__(self, cols, rows, name, column_offset=0):
super().__init__(cols, rows, name, column_offset)
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 create_layout(self):
self.place_array("bit_r{0}_c{1}")
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
""" Add the modules used in this design """
self.cell = factory.create(module_type=OPTS.bitcell)
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.cell)
self.connect_inst(self.get_bitcell_pins(col, row))
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_wl_wire(self):
if OPTS.netlist_only:
width = 0
else:
width = self.width
wl_wire = self.generate_rc_net(int(self.column_size), width, drc("minwidth_m1"))
wl_wire.wire_c = 2 * spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
return wl_wire
def gen_bl_wire(self):
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 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
def graph_exclude_bits(self, targ_row, targ_col):
"""Excludes bits in column from being added to graph except target"""
# Function is not robust with column mux configurations
for row in range(self.row_size):
for col in range(self.column_size):
if row == targ_row and col == targ_col:
continue
self.graph_inst_exclude.add(self.cell_inst[row, col])
def get_cell_name(self, inst_name, row, col):
"""Gets the spice name of the target bitcell."""
return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col]

219
compiler/modules/pattern.py Normal file
View File

@ -0,0 +1,219 @@
# 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.geometry import instance,geometry
from typing import List
from typing import Optional
from openram.base import design
from openram.globals import OPTS
from math import ceil, floor
from copy import deepcopy
class pattern():
"""
This class is used to desribe the internals of a bitcell array. It describes
instance modules, rotation, and ordering
"""
block = List[List[instance]]
def __init__(self,
parent_design: design,
name:str,
core_block:block,
num_rows:int,
num_cols:int,
name_template,
num_cores_x: Optional[int] = 0,
num_cores_y: Optional[int] = 0,
cores_per_x_block: int = 1,
cores_per_y_block: int = 1,
x_block: Optional[block] = None,
y_block: Optional[block] = None,
xy_block: Optional[block] = None,
initial_x_block:bool = False,
initial_y_block:bool = False,
final_x_block:bool = False,
final_y_block:bool = False,
):
"""
a "block" is a 2d list of instances
core_block defines the main block that is tiled
num_core defines the number of times the core block is to be tiled
(i.e. bitcells in dimension / bitcells in core_block)
x_block defines a block that is inserted to the right of the core_block
y_block defines a block that is inserted below the core block
xy_block defines a block that is a inserted where the x_block and y_block intercept
the initial and final booleans determine whether the pattern begins and/or ends with x/y blocks
"""
self.parent_design = parent_design
self.name = name
self.core_block = core_block
self.num_rows = num_rows
self.num_cols = num_cols
self.name_template = name_template
self.num_cores_x = num_cores_x
self.num_cores_y = num_cores_y
if num_cores_x == 0:
self.num_cores_x = ceil(num_cols/len(core_block[0]))
if num_cores_y == 0:
self.num_cores_y = ceil(num_rows/len(core_block))
self.cores_per_x_block = cores_per_x_block
self.cores_per_y_block = cores_per_y_block
self.x_block = x_block
self.y_block = y_block
self.xy_block = xy_block
self.initial_x_block = initial_x_block
self.initial_y_block = initial_y_block
self.final_x_block = final_x_block
self.final_y_block = final_y_block
self.bits_per_row = ceil(self.num_rows/self.num_cores_x)
self.bits_per_col = ceil(self.num_cols/self.num_cores_y)
self.bit_rows = []
self.bit_cols = []
self.parent_design.all_inst = {}
if not OPTS.netlist_only:
self.verify_interblock_dimensions()
def compute_and_verify_intrablock_dimensions(self, block: block) -> List[int]:
for row in block:
row_height = row[0].height
for inst in row:
debug.check(row_height == inst.height, "intrablock instances within the same row are different heights")
for y in range(len(block[0])):
debug.check(all([row[y].width for row in block]), "intrablock instances within the same column are different widths")
block_width = sum([instance.width for instance in block[0]])
block_height = sum([row[0].height for row in block])
return [block_width, block_height]
def verify_interblock_dimensions(self) -> None:
"""
Ensure the individual blocks are valid and interblock dimensions are valid
"""
debug.check(len(self.core_block) >= 1, "invalid core_block dimension: core_block rows must be >=1")
debug.check(len(self.core_block[0]) >= 1, "invalid core_block dimension: core_block cols must be >=1")
if self.x_block and self.y_block:
debug.check(self.xy_block is not None, "must have xy_block if both x_block and y_block are provided")
(self.core_block_width, self.core_block_height) = self.compute_and_verify_intrablock_dimensions(self.core_block)
if self.x_block:
(self.x_block_width, self.x_block_height) = self.compute_and_verify_intrablock_dimensions(self.x_block)
if self.y_block:
(self.y_block_width, self.y_block_height) = self.compute_and_verify_intrablock_dimensions(self.y_block)
if self.xy_block:
(self.xy_block_width, self.xy_block_height) = self.compute_and_verify_intrablock_dimensions(self.xy_block)
if(self.x_block):
debug.check(self.core_block_width * self.cores_per_x_block == self.x_block_width, "core_block does not align with x_block")
if(self.y_block):
debug.check(self.core_block_height * self.cores_per_y_block == self.y_block_height, "core_block does not aligns with y_block")
if(self.xy_block):
debug.check(self.xy_block_height == self.x_block_height, "xy_block does not align with x_block")
debug.check(self.xy_block_width == self.y_block_width, "xy_block does not align with y_block")
def connect_block(self, block: block, col: int, row: int):
for dr in range(len(block)):
row_done = False
for dc in range(len(block[0])):
if(self.bit_rows.count(self.num_rows) <= self.num_cols and self.bit_cols.count(self.num_cols) <= self.num_rows):
inst = block[dr][dc]
if(len(self.bit_rows) <= col + dc):
self.bit_rows.append(0)
if(len(self.bit_cols) <= row + dr):
self.bit_cols.append(0)
if(row_done or self.bit_cols[row+dr] >= self.num_cols):
row_done = True
continue
if((self.bit_rows[col+dc] < self.num_rows) and (self.bit_cols[row+dr] < self.num_cols)):
if(inst.is_bitcell):
self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc))
self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.cell_inst[self.bit_rows[col+dc], self.bit_cols[row+dr]]
self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col+dc], self.bit_cols[row+dr]))
self.bit_rows[col+dc] += 1
self.bit_cols[row+dr] += 1
else:
self.parent_design.all_inst[row + dr, col + dc] = self.parent_design.add_existing_inst(inst,self.name_template.format(row +dr, col+dc))
self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col+dc], self.bit_cols[row+dr]))
else:
row_done = True
def connect_array(self) -> None:
row = 0
col = 0
for i in range(self.num_cores_y):
for j in range (self.num_cores_x):
self.connect_block(self.core_block, col, row)
col += len(self.core_block[0])
col = 0
row += len(self.core_block)
def connect_array_raw(self) -> None:
for row in range(self.num_rows):
for col in range(self.num_cols):
inst = self.core_block[row][col]
if(len(self.bit_rows) <= col):
self.bit_rows.append(0)
if(len(self.bit_cols) <= row):
self.bit_cols.append(0)
if(inst.is_bitcell):
self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col))
self.parent_design.all_inst[row, col] = self.parent_design.cell_inst[self.bit_rows[col], self.bit_cols[row]]
self.parent_design.connect_inst(self.parent_design.get_bitcell_pins(self.bit_rows[col], self.bit_cols[row]))
self.bit_rows[col] += 1
self.bit_cols[row] += 1
else:
self.parent_design.all_inst[row, col] = self.parent_design.add_existing_inst(inst,self.name_template.format(row, col))
self.parent_design.connect_inst(self.parent_design.get_strap_pins(self.bit_rows[col], self.bit_cols[row]))
def place_inst(self, inst, offset) -> None:
x = offset[0]
y = offset[1]
if "X" in inst.mirror:
y += inst.height
if "Y" in inst.mirror:
x += inst.width
#print('placing inst {} at {}'.format(inst, offset))
inst.place((x, y), inst.mirror, inst.rotate)
def place_array(self):
(self.row_max, self.col_max) = list(self.parent_design.all_inst.keys())[-1]
y = 0
for row in range(self.row_max+1):
x = 0
for col in range(self.col_max+1):
inst = self.parent_design.all_inst[row, col]
self.place_inst(inst, (x, y))
x += inst.width
y += inst.height
self.parent_design.width = max([x.rx() for x in self.parent_design.insts])
self.parent_design.height = max([x.uy() for x in self.parent_design.insts])
def append_row_to_block(block, row):
block.append(row)
def append_block_under_block(base_block, under_block):
base_block = base_block + under_block
def append_block_right_block(base_block, right_block):
for row in base_block:
row = row + right_block
def rotate_list(lst, n):
# Loop through the range of n positions to rotate
for i in range(n):
# Remove the last element of the list and insert it at the beginning
lst.insert(0, lst.pop())
# Return the rotated list
return lst

View File

@ -156,13 +156,15 @@ class pgate(design):
return via
def extend_wells(self):
def extend_wells(self, width=None):
""" Extend the n/p wells to cover whole cell """
# This should match the cells in the cell library
self.nwell_yoffset = 0.48 * self.height
full_height = self.height + 0.5 * self.m1_width
if width == None:
width = self.width + 2 * self.well_extend_active
# FIXME: float rounding problem
if "nwell" in layer:
@ -173,12 +175,12 @@ class pgate(design):
nwell_height = nwell_max_offset - self.nwell_yoffset
self.add_rect(layer="nwell",
offset=nwell_position,
width=self.width + 2 * self.well_extend_active,
width=width,
height=nwell_height)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=nwell_position,
width=self.width + 2 * self.well_extend_active,
width=width,
height=nwell_height)
# Start this half a rail width below the cell
@ -189,12 +191,12 @@ class pgate(design):
pwell_height = self.nwell_yoffset - pwell_position.y
self.add_rect(layer="pwell",
offset=pwell_position,
width=self.width + 2 * self.well_extend_active,
width=width,
height=pwell_height)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=pwell_position,
width=self.width + 2 * self.well_extend_active,
width=width,
height=pwell_height)
if cell_props.pgate.add_implants:

View File

@ -4,6 +4,8 @@
# All rights reserved.
#
from openram import debug
from openram.base import round_to_grid
from openram.tech import drc
from openram.base import vector
from openram.base import contact
from openram.sram_factory import factory
@ -22,8 +24,8 @@ class replica_bitcell_array(bitcell_base_array):
Requires a regular bitcell array and (if using replica topology)
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)
def __init__(self, rows, cols, rbl=None, left_rbl=None, right_rbl=None, column_offset=0, row_offset=0, name=""):
super().__init__(name=name, rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset)
debug.info(1, "Creating {0} {1} x {2} rbls: {3} left_rbl: {4} right_rbl: {5}".format(self.name,
rows,
cols,
@ -33,6 +35,9 @@ class replica_bitcell_array(bitcell_base_array):
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_offset=column_offset
self.row_offset=row_offset
self.column_size = cols
self.row_size = rows
# This is how many RBLs are in all the arrays
@ -74,9 +79,12 @@ class replica_bitcell_array(bitcell_base_array):
""" Array and dummy/replica columns """
# Bitcell array
self.bitcell_array = factory.create(module_type="bitcell_array",
column_offset=1 + len(self.left_rbl),
column_offset=len(self.left_rbl)+ self.column_offset,
row_offset=len(self.left_rbl)+ self.row_offset,
cols=self.column_size,
rows=self.row_size)
rows=self.row_size,
left_rbl=self.left_rbl,
right_rbl=self.right_rbl)
# Replica bitlines
self.replica_columns = {}
@ -87,29 +95,36 @@ class replica_bitcell_array(bitcell_base_array):
if port in self.left_rbl:
# These go top down starting from the bottom of the bitcell array.
replica_bit = self.rbl[0] - port - 1
column_offset = len(self.left_rbl)
rbc_offset = 0
elif port in self.right_rbl:
# These go bottom up starting from the top of the bitcell array.
replica_bit = self.rbl[0] + self.row_size + port - 1
column_offset = len(self.left_rbl) + self.column_size + 1
rbc_offset = len(self.left_rbl) + self.column_size
else:
continue
self.replica_columns[port] = factory.create(module_type="replica_column",
rows=self.row_size,
rbl=self.rbl,
column_offset=column_offset,
column_offset=rbc_offset + self.column_offset,
replica_bit=replica_bit)
# Dummy row (for replica wordlines)
self.dummy_row = factory.create(module_type="dummy_array",
self.dummy_rows = {}
for port in self.all_ports:
if port in self.left_rbl:
dummy_offset = 0
elif port in self.right_rbl:
dummy_offset = self.row_size + len(self.left_rbl)
else:
dummy_offset = 0
self.dummy_rows[port] = factory.create(module_type="dummy_array",
cols=self.column_size,
rows=1,
# cap column + left replica column
# FIXME: these col offsets should really start at 0 because
# this is the left edge of the array... but changing them all is work
column_offset=1 + len(self.left_rbl),
mirror=0)
row_offset=dummy_offset + self.row_offset,
column_offset=len(self.left_rbl) + self.row_offset)
def add_pins(self):
@ -213,7 +228,7 @@ class replica_bitcell_array(bitcell_base_array):
for port in self.all_ports: # TODO: tie to self.rbl or whatever
if self.rbl[port] != 0:
self.dummy_row_replica_insts.append(self.add_inst(name="dummy_row_{}".format(port),
mod=self.dummy_row))
mod=self.dummy_rows[port]))
self.connect_inst(self.all_bitline_names + self.rbl_wordline_names[port] + self.supplies)
else:
self.dummy_row_replica_insts.append(None)
@ -225,28 +240,28 @@ class replica_bitcell_array(bitcell_base_array):
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]))
# 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=0)
self.bitcell_array_inst.place(offset=(0,0))
self.add_replica_columns()
# 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(-len(self.left_rbl), -self.rbl[0])
self.translate_all(array_offset)
#rbc_width = (self.replica_col_insts[0].width, 0)
#dummy_height = max(x for x in map(lambda x: x if x != None else 0, self.))
#array_offset = self.bitcell_offset.scale(-len(self.left_rbl), -self.rbl[0])
ll=vector(min([x.lx() for x in self.insts]),min([y.by() for y in self.insts]))
self.translate_all(ll)
self.add_layout_pins()
self.route_supplies()
self.height = (sum(self.rbl) + self.row_size) * self.cell.height
self.width = (len(self.rbls) + self.column_size) * self.cell.width
self.width = max([x.rx() for x in self.insts]) - min([x.lx() for x in self.insts])
self.height = max([x.uy() for x in self.insts]) - min([y.by() for y in self.insts])
self.add_boundary()
@ -280,25 +295,24 @@ class replica_bitcell_array(bitcell_base_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])
rbc_dimension = vector(self.replica_col_insts[port].width, self.cell.height)
offset = rbc_dimension.scale(-len(self.left_rbl) + bit, -self.rbl[0])
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])
offset = self.bitcell_array_inst.lr() + rbc_dimension.scale(bit, -self.rbl[0])
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.dummy_row_replica_insts[bit].place(offset=dummy_offset,
mirror="MX" if (-self.rbl[0] + bit) % 2 else "R0")
dummy_offset = self.bitcell_array_inst.ll() - vector(0, self.dummy_row_replica_insts[bit].height)
self.dummy_row_replica_insts[bit].place(offset=dummy_offset)
# 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")
dummy_offset = self.bitcell_array_inst.ul()
self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset)
def add_layout_pins(self):
""" Add the layout pins """
@ -316,14 +330,15 @@ class replica_bitcell_array(bitcell_base_array):
# 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()):
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())
if self.rbl != [0,0]:
for (names, inst) in zip(self.rbl_wordline_names, self.dummy_row_replica_insts):
for (wl_name, pin_name) in zip(names, self.dummy_rows[0].get_wordline_names()):
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())
# Main array bl/br
for pin_name in self.all_bitline_names:
@ -347,12 +362,6 @@ class replica_bitcell_array(bitcell_base_array):
width=pin.width(),
height=self.height)
def route_supplies(self):
""" just copy supply pins from all instances """
for inst in self.insts:
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""
# Dynamic Power from Bitline

View File

@ -9,7 +9,8 @@ from openram.sram_factory import factory
from openram.tech import layer_properties as layer_props
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
from openram.base import geometry
from openram.modules import pattern
class replica_column(bitcell_base_array):
"""
@ -29,7 +30,7 @@ class replica_column(bitcell_base_array):
self.row_start = rbl[0]
# End of regular word line rows
self.row_end = self.row_start + rows
super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name)
super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, row_offset=0, name=name)
self.rows = rows
self.left_rbl = rbl[0]
@ -44,10 +45,10 @@ class replica_column(bitcell_base_array):
debug.check(replica_bit < self.row_start or replica_bit >= self.row_end,
"Replica bit cannot be in the regular array.")
if layer_props.replica_column.even_rows:
debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0,
"sky130 currently requires rows to be even and to start with X mirroring"
+ " (left_rbl must be odd) for LVS.")
#if layer_props.replica_column.even_rows:
# debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0,
# "sky130 currently requires rows to be even and to start with X mirroring"
# + " (left_rbl must be odd) for LVS.")
self.create_netlist()
if not OPTS.netlist_only:
@ -59,10 +60,7 @@ class replica_column(bitcell_base_array):
self.create_instances()
def create_layout(self):
self.place_instances()
self.height = self.cell_inst[-1].uy()
self.width = self.cell_inst[0].rx()
self.place_array()
self.add_layout_pins()
@ -88,103 +86,38 @@ class replica_column(bitcell_base_array):
self.dummy_cell = factory.create(module_type=OPTS.dummy_bitcell)
def create_instances(self):
self.cell_inst = []
self.cell_inst = {}
core_block = [[0 for x in range(1)] for y in range(self.total_size)]
current_row = self.row_start
for row in range(self.total_size):
name = "rbc_{0}".format(row)
# Regular array cells are replica cells
# Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell.
# All other cells are dummies
if (row == self.replica_bit) or (row >= self.row_start and row < self.row_end):
self.cell_inst.append(self.add_inst(name=name,
mod=self.replica_cell))
self.connect_inst(self.get_bitcell_pins(row, 0))
if current_row % 2 == 0:
core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.replica_cell, is_bitcell=True, mirror='MY')
else:
core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.replica_cell, is_bitcell=True, mirror='XY')
else:
self.cell_inst.append(self.add_inst(name=name,
mod=self.dummy_cell))
self.connect_inst(self.get_bitcell_pins(row, 0))
if current_row % 2 == 0:
core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.dummy_cell, is_bitcell=True, mirror='MY')
else:
core_block[row][0] = geometry.instance("rbc_{}".format(row), mod=self.dummy_cell, is_bitcell=True, mirror='XY')
def place_instances(self):
# Flip the mirrors if we have an odd number of replica+dummy rows at the bottom
# so that we will start with mirroring rather than not mirroring
rbl_offset = (self.left_rbl) % 2
# if our bitcells are mirrored on the y axis, check if we are in global
# column that needs to be flipped.
dir_y = False
xoffset = 0
if self.cell.mirror.y and self.column_offset % 2:
dir_y = True
xoffset = self.replica_cell.width
for row in range(self.total_size):
# name = "bit_r{0}_{1}".format(row, "rbl")
dir_x = self.cell.mirror.x and (row + rbl_offset) % 2
offset = vector(xoffset, self.cell.height * (row + (row + rbl_offset) % 2))
if dir_x and dir_y:
dir_key = "XY"
elif dir_x:
dir_key = "MX"
elif dir_y:
dir_key = "MY"
else:
dir_key = ""
self.cell_inst[row].place(offset=offset,
mirror=dir_key)
def add_layout_pins(self):
for port in self.all_ports:
bl_pin = self.cell_inst[0].get_pin(self.cell.get_bl_name(port))
self.add_layout_pin(text="bl_{0}_{1}".format(port, 0),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
bl_pin = self.cell_inst[0].get_pin(self.cell.get_br_name(port))
self.add_layout_pin(text="br_{0}_{1}".format(port, 0),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
for port in self.all_ports:
current_row += 1
if self.cell.mirror.y:
for row in range(self.total_size):
wl_pin = self.cell_inst[row].get_pin(self.cell.get_wl_name(port))
self.add_layout_pin(text="wl_{0}_{1}".format(port, row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
if self.column_offset % 2 == 0:
if core_block[row][0].mirror=='MY':
core_block[row][0].mirror=''
else:
core_block[row][0].mirror='MX'
def route_supplies(self):
for inst in self.cell_inst:
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
def get_bitline_names(self, port=None):
if port == None:
return self.all_bitline_names
else:
return self.bitline_names[port]
def get_bitcell_pins(self, row, col):
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""
bitcell_pins = []
for port in self.all_ports:
bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))])
bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
bitcell_pins.append("vdd")
bitcell_pins.append("gnd")
return bitcell_pins
self.pattern = pattern(self, "bitcell_array", core_block, num_rows=self.total_size, num_cols=self.column_size, name_template="rbc_r{0}_c{1}")
self.pattern.connect_array()
def get_bitcell_pins_col_cap(self, row, col):
"""
@ -208,4 +141,4 @@ class replica_column(bitcell_base_array):
for row, cell in enumerate(self.cell_inst):
if row != self.replica_bit:
self.graph_inst_exclude.add(cell)
self.graph_inst_exclude.add(self.cell_inst[cell])

View File

@ -6,16 +6,21 @@
from openram.sram_factory import factory
from openram import OPTS
from .bitcell_base_array import bitcell_base_array
from .pattern import pattern
from openram.base import geometry
from math import ceil
class row_cap_array(bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, column_offset=0, mirror=0, name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name)
def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name)
self.mirror = mirror
self.no_instances = True
self.location = location
self.row_offset = row_offset
self.column_offset = column_offset
#self.no_instances = True
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
@ -32,7 +37,7 @@ class row_cap_array(bitcell_base_array):
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.place_array()
self.add_layout_pins()
self.width = max([x.rx() for x in self.insts])
@ -43,19 +48,41 @@ class row_cap_array(bitcell_base_array):
def add_modules(self):
""" Add the modules used in this design """
self.dummy_cell = factory.create(module_type="row_cap_{}".format(OPTS.bitcell))
self.row_cap = factory.create(module_type="row_cap_{}".format(OPTS.bitcell))
self.cell = factory.create(module_type=OPTS.bitcell)
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
for col in range(self.column_size):
for row in range(0, self.row_size):
name = "bit_r{0}_c{1}".format(row, col)
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.dummy_cell)
self.connect_inst(self.get_bitcell_pins(row, col))
self.all_inst={}
self.cell_inst={}
bit_block = []
if self.column_offset % 2 == 0:
#top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False, mirror="MY")
#bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="XY")
rowend = geometry.instance("row_cap_rowend", mod=self.row_cap, is_bitcell=True, mirror="MX")
rowend_m = geometry.instance("row_cap_rowend_m", mod=self.row_cap, is_bitcell=True, mirror="")
else:
#top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False)
#bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="MX")
rowend = geometry.instance("row_cap_rowend", mod=self.row_cap, is_bitcell=True, mirror="XY")
rowend_m = geometry.instance("row_cap_rowend_m", mod=self.row_cap, is_bitcell=True, mirror="MY")
#pattern.append_row_to_block(bit_block, [top_corner])
for row in range(0, self.row_size):
if row % 2 == 0:
pattern.append_row_to_block(bit_block, [rowend])
else:
pattern.append_row_to_block(bit_block, [rowend_m])
#pattern.append_row_to_block(bit_block, [bottom_corner])
if self.cell.has_corners is False:
num_rows = self.row_size - 2
else:
num_rows = self.row_size
self.pattern = pattern(self, "row_cap_array_" + self.location, bit_block, num_rows=num_rows, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="row_cap_array" + self.location + "_r{0}_c{1}")
self.pattern.connect_array_raw()
def get_bitcell_pins(self, row, col):
"""
@ -69,44 +96,21 @@ class row_cap_array(bitcell_base_array):
return bitcell_pins
def place_array(self, name_template, row_offset=0):
xoffset = 0.0
for col in range(self.column_size):
yoffset = self.cell.height
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
for row in range(self.row_size):
tempy, dir_x = self._adjust_y_offset(yoffset, row + 1, row_offset)
if dir_x and dir_y:
dir_key = "XY"
elif dir_x:
dir_key = "MX"
elif dir_y:
dir_key = "MY"
else:
dir_key = ""
self.cell_inst[row, col].place(offset=[tempx, tempy],
mirror=dir_key)
yoffset += self.cell.height
xoffset += self.cell.width
def add_layout_pins(self):
""" Add the layout pins """
row_list = self.cell.get_all_wl_names()
for row in range(1, self.row_size - 1):
for cell_row in row_list:
wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
self.add_layout_pin(text=cell_row + "_{0}".format(row),
wl_names = self.cell.get_all_wl_names()
max_row = self.row_size - 2
for row in range(0, max_row):
for port in self.all_ports:
wl_pin = self.cell_inst[row, 0].get_pin(wl_names[port])
self.add_layout_pin(text="wl_{0}_{1}".format(port, row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
for row in range(1, self.row_size - 1):
for row in range(0, max_row):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:

View File

@ -662,7 +662,10 @@ class sram_1bank(design, verilog, lef):
inputs = []
outputs = []
for bit in range(self.num_spare_cols):
inputs.append("spare_wen{}[{}]".format(port, bit))
if self.num_spare_cols == 1:
inputs.append("spare_wen{}".format(port))
else:
inputs.append("spare_wen{}[{}]".format(port, bit))
outputs.append("bank_spare_wen{}_{}".format(port, bit))
self.connect_inst(inputs + outputs + ["clk_buf{}".format(port)] + self.ext_supplies)

View File

@ -11,6 +11,7 @@ from openram.base import design
from openram.base import vector
from openram.sram_factory import factory
from openram import OPTS
from openram.tech import drc
class write_mask_and_array(design):
@ -104,6 +105,10 @@ class write_mask_and_array(design):
base = vector(self.offsets[int(i * write_bits)], 0)
self.and2_insts[i].place(base)
# decide whethre to connect nwell to avoid drc errors
if self.bitcell.width < drc("nwell_to_nwell"):
self.and2.extend_wells(width=self.and2.width + drc("nwell_to_nwell"))
def add_layout_pins(self):
# Create the enable pin that connects all write mask AND array's B pins

View File

@ -151,9 +151,9 @@ class options(optparse.Values):
###################
# Top process that was ran (openram, memchar, memfunc)
top_process = None
# Use conda to install the default tools
# (existing tools will be used if disabled)
use_conda = True
# Use Nix to initialize the default open-source toolchain.
# If disabled, OpenRAM uses whatever tools are already in PATH.
use_nix = True
# Variable to select the variant of spice
spice_name = None
# The spice executable being used which is derived from the user PATH.

View File

@ -141,12 +141,12 @@ class sram_config:
+ " Bank addr size: {}".format(self.bank_addr_size))
num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports
if num_ports == 1:
if ((self.num_cols + num_ports + self.num_spare_cols) % self.array_col_multiple != 0):
debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.num_cols + num_ports + self.num_spare_cols, self.array_col_multiple), -1)
if ((self.num_rows + num_ports) % self.array_row_multiple != 0):
debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.num_rows + num_ports, self.array_row_multiple), -1)
#if num_ports == 1:
# if ((self.num_cols + num_ports + self.num_spare_cols) % self.array_col_multiple != 0):
# debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.num_cols + num_ports + self.num_spare_cols, self.array_col_multiple), -1)#
#
# if ((self.num_rows + num_ports) % self.array_row_multiple != 0):
# debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.num_rows + num_ports, self.array_row_multiple), -1)
def estimate_words_per_row(self, tentative_num_cols, word_size):
"""

View File

@ -31,7 +31,7 @@ class array_test(openram_test):
num_spare_rows = 0
num_spare_cols = 0
a = factory.create(module_type="bitcell_array", cols=8 + num_spare_cols, rows=8 + num_spare_rows)
a = factory.create(module_type="bitcell_array", cols=2 + num_spare_cols, rows=2 + num_spare_rows)
self.local_check(a)
openram.end_openram()

View File

@ -26,7 +26,7 @@ class capped_replica_bitcell_array_norbl_1rw_test(openram_test):
openram.setup_bitcell()
debug.info(2, "Testing 7x5 capped replica array for 1rw cell without replica column or dummy row")
a = factory.create(module_type="capped_replica_bitcell_array", cols=7, rows=5, rbl=[0, 0])
a = factory.create(module_type="capped_replica_bitcell_array", cols=8, rows=6, rbl=[0, 0])
self.local_check(a)
openram.end_openram()

View File

@ -23,6 +23,7 @@ class replica_bitcell_array_leftrbl_1rw_test(openram_test):
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 0
OPTS.num_w_ports = 0
openram.setup_bitcell()
debug.info(2, "Testing 7x5 replica array for 1rw cell with left replica column")

View File

@ -24,7 +24,9 @@ class single_bank_test(openram_test):
from openram import sram_config
c = sram_config(word_size=4,
num_words=16)
num_words=16,
num_spare_cols=1,
num_spare_rows=1)
c.num_words=32
c.words_per_row=2

View File

@ -25,7 +25,7 @@ class sram_1bank_nomux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.num_sim_threads = 1
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
from openram import characterizer

View File

@ -1,3 +1,4 @@
SHELL := /bin/bash
TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))../..)
include $(TOP_DIR)/openram.mk
@ -175,7 +176,11 @@ $(TEST_BASES):
@rm -rf results/$*
@rm -rf results/$*.*
@mkdir -p results/$*/tmp
ifdef TECH_FILE
@sh -c "python3 -u $(OPENRAM_DIR)/$(getfile).py -t $(gettech) -k -f $(TECH_FILE) -v $(ARGS) -p $(OPENRAM_DIR)/results/$* > $(OPENRAM_DIR)/results/$*.out 2>&1 && touch $(OPENRAM_DIR)/results/$*.ok || touch $(OPENRAM_DIR)/results/$*.bad"
else
@sh -c "python3 -u $(OPENRAM_DIR)/$(getfile).py -t $(gettech) -k -v $(ARGS) -p $(OPENRAM_DIR)/results/$* > $(OPENRAM_DIR)/results/$*.out 2>&1 && touch $(OPENRAM_DIR)/results/$*.ok || touch $(OPENRAM_DIR)/results/$*.bad"
endif
ifdef KEEP
@test -f $(TOP_DIR)/compiler/tests/results/$*.ok && echo "$* ... PASS!" || echo "$* ... FAIL!"
else

View File

@ -833,8 +833,8 @@ Xrca_top_4
X0 ll WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 ul Q_bar_float VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL ul VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q_float lr VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 ur Q_bar_float VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q_float ll VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
@ -977,8 +977,8 @@ Xrca_19
X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
@ -989,8 +989,8 @@ X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15

View File

@ -833,8 +833,8 @@ Xrca_top_4
X0 ll WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 ul Q_bar_float VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL ul VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 ur WL ur VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 lr WL lr VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q_float lr VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 ur Q_bar_float VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q_float ll VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
@ -977,8 +977,8 @@ Xrca_19
X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
@ -989,8 +989,8 @@ X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X0 Q_bar WL BR VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
X1 Q Q_bar VGND VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15
X2 BL WL Q VNB sky130_fd_pr__special_nfet_pass w=0.14 l=0.15
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.07 l=0.095
*X3 Q WL Q VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
*X4 Q_bar WL Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14u l=25n
X5 VPWR Q Q_bar VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X6 Q Q_bar VPWR VPB sky130_fd_pr__special_pfet_pass w=0.14 l=0.15
X7 VGND Q Q_bar VNB sky130_fd_pr__special_nfet_latch w=0.21 l=0.15

View File

@ -29,26 +29,21 @@ def run_script(cell_name, script="lvs"):
scriptpath = '{0}run_{1}.sh'.format(OPTS.openram_temp, script)
# Wrap with conda activate & conda deactivate
if OPTS.use_conda:
from openram import CONDA_HOME
with open(scriptpath, "r") as f:
script_content = f.readlines()
with open(scriptpath, "w") as f:
# First line is shebang
f.write(script_content[0])
# Activate conda using the activate script
f.write("source {}/bin/activate\n".format(CONDA_HOME))
for line in script_content[1:]:
f.write(line)
# Deactivate conda at the end
f.write("conda deactivate\n")
debug.info(2, "Starting {}".format(scriptpath))
start = time.time()
with open(outfile, 'wb') as fo, open(errfile, 'wb') as fe:
if OPTS.use_nix:
p_cmd = [
"nix",
"--extra-experimental-features", "nix-command flakes",
"develop",
"--command",
scriptpath,
]
else:
p_cmd = [scriptpath]
p = subprocess.Popen(
[scriptpath], stdout=fo, stderr=fe, cwd=OPTS.openram_temp)
p_cmd, stdout=fo, stderr=fe, cwd=OPTS.openram_temp)
if echo_cmd_output:
tailo = subprocess.Popen([

View File

@ -7,8 +7,8 @@ This page shows the basic setup for using OpenRAM to generate an SRAM.
## Table of Contents
1. [Dependencies](#dependencies)
1. [Anaconda](#anaconda)
1. [Docker](#docker-deprecated-use-anaconda-instead)
1. [Nix](#nix)
1. [Docker](#docker-deprecated-use-nix-instead)
1. [Environment](#environment)
1. [Sky130 Setup](#sky130-setup)
@ -20,51 +20,25 @@ In general, the OpenRAM compiler has very few dependencies:
+ Make
+ Python 3.5 or higher
+ Various Python packages (pip install -r requirements.txt)
+ Anaconda
+ Nix
## Anaconda
We use Anaconda package manager to install the tools used by OpenRAM. This way,
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).
## Nix
OpenRAM uses Nix to provide the external toolchain (layout tools, simulators,
etc.) needed for SRAM generation.
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:
Enter the Nix development environment with:
```
export CONDA_HOME="/path/to/conda/setup"
nix develop
```
> **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
> ```
Within the devShell, required executables are available on `PATH`
> **Note**: You can uninstall OpenRAM's Anaconda installation by simply deleting
> the folder Anaconda is installed to. You can run:
> ```
> rm -rf miniconda
> ```
OpenRAM uses the `use_nix` option (enabled by default) to initialize Nix-based
tool dependencies via `nix develop`.
> **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)
## Docker (deprecated, use Nix instead)
We have a [docker setup](../../docker) to run OpenRAM. To use this, you should
run:
```
@ -124,12 +98,11 @@ make sky130-pdk
This will use ciel to get the PDK.
> **Note**: If you don't have Magic installed, you need to install and activate
> the conda environment before running this command. You can run:
> **Note**: If you don't have Magic installed, enter the OpenRAM Nix devShell
> first (it provides Magic and other tools via `PATH`):
>
> ```
> ./install_conda.sh
> source miniconda/bin/activate
> nix develop
> ```
Then you must also install the [Sky130] SRAM build space with the appropriate

View File

@ -61,10 +61,9 @@ make -j 3
```
The `-j` can run with 3 threads. By default, this will run in all technologies.
> **Note**: If you have not run openram before running unit tests, the conda
> environment will not be installed. You can install it by running
> `OpenRAM/install_conda.sh` (see [Basic Setup](basic_setup.md#anaconda) for
> more details).
> **Note**: The external EDA toolchain is provided by the Nix devShell.
> If you run unit tests without being in a Nix environment, enter it first:
> `nix develop` (see [Basic Setup](basic_setup.md#nix) for more details).
To run a specific test in all technologies:
```

View File

@ -40,7 +40,7 @@ In general, the OpenRAM compiler has very few dependencies:
+ Make
+ Python 3.5 or higher
+ Various Python packages (pip install -r requirements.txt)
+ Anaconda
+ Nix
Commercial tools (optional):
* Spice Simulator

View File

@ -11,7 +11,7 @@ In this section, the detailed usage of using OpenRAM framework will be demonstra
>
> Before you go through, make sure that environment of Sky130 has been already set up.
1. Activate miniconda
1. Enter the Nix devShell
2. Edit the sram configuration file
@ -19,11 +19,11 @@ In this section, the detailed usage of using OpenRAM framework will be demonstra
4. Check the results
#### Activate miniconda
#### Enter Nix devShell
```bash
cd OpenRAM/
source ./miniconda/bin/activate
nix develop
```
#### Modified the sram configuration

27
flake.lock Normal file
View File

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1777270315,
"narHash": "sha256-yKB4G6cKsQsWN7M6rZGk6gkJPDNPIzT05y4qzRyCDlI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6368eda62c9775c38ef7f714b2555a741c20c72d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

48
flake.nix Normal file
View File

@ -0,0 +1,48 @@
{
description = "OpenRAM development environment (Nix)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
in
{
devShells = forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
default = pkgs.mkShell {
packages = [
# EDA / verification tools
pkgs.klayout
pkgs.magic-vlsi
# Use the LVS-focused netgen package; the generic netgen package
# may require a local build that can fail on some hosts.
pkgs.netgen-vlsi
pkgs.ngspice
pkgs.iverilog
pkgs.xyce
pkgs.xyce-parallel
pkgs.trilinos
pkgs.trilinos-mpi
# Dev conveniences
pkgs.git
pkgs.gnumake
pkgs.curl
];
shellHook = ''
export OPENRAM_USE_CONDA=0
echo "OpenRAM: using tools from Nix devShell"
'';
};
});
};
}

View File

@ -1,43 +0,0 @@
#!/bin/bash
CONDA_INSTALLER_URL="https://repo.anaconda.com/miniconda/Miniconda3-py38_23.11.0-2-Linux-x86_64.sh"
CONDA_INSTALLER_FILE="miniconda_installer_py38.sh"
CONDA_HOME="${CONDA_HOME:-miniconda}"
# The tool name format is "<tool>=<version>".
# If you want to use the latest version, just use "<tool>".
TOOLS=""
TOOLS+="klayout=0.28.3 "
TOOLS+="magic=8.3.363 "
TOOLS+="netgen=1.5.253 "
TOOLS+="ngspice=26 "
TOOLS+="trilinos=12.12.1=1 "
TOOLS+="xyce=7.4=3"
# 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_HOME}
rm ${CONDA_INSTALLER_FILE}
source ${CONDA_HOME}/bin/activate
# Prioritize channels to prevent version conflicts
conda config --add channels conda-forge
conda config --add channels vlsida-eda
# Install iverilog from conda-eda
conda install -q -y -c litex-hub iverilog
# Install rest of the tools from vlsida-eda
for tool in ${TOOLS}
do
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 --ignore-installed
conda deactivate
fi

View File

@ -65,6 +65,9 @@ all: | configs
example: $(EXAMPLE_STAMPS)
.PHONY: example
sp: sky130_sram_1kbyte_1rw_32x256_8 sky130_sram_2kbyte_1rw_32x512_8 sky130_sram_4kbyte_1rw_32x1024_8 sky130_sram_4kbyte_1rw_64x512_8
.PHONY: sp
sky130: $(SKY130_STAMPS)
.PHONY: sky130

View File

@ -4,12 +4,13 @@ Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write.
FIXME: What is this useful for?
FIXME: Why would you want byte write on this?
"""
word_size = 8 # Bits
word_size = 16 # Bits
num_words = 16
human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8)
# Allow byte writes
write_size = 2 # Bits
write_size = 8 # Bits
words_per_row = 1
# Dual port
num_rw_ports = 1
@ -19,6 +20,10 @@ ports_human = '1rw'
num_spare_cols = 1
num_spare_rows = 1
#netlist_only = True
num_sim_threads = 1
#analytical_delay = False
import os
exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read())
tech_file = "tech_custom_cell"

View File

@ -11,7 +11,7 @@ from setuptools import setup, find_namespace_packages
# Include these folder from the root of repo as submodules
include = ["compiler", "docker", "technology", "macros"]
# Exclude files/folders with these words
exclude = ["docs", "images", "miniconda"]
exclude = ["docs", "images"]
# Find all modules inside the 'compiler' folder

View File

@ -1,731 +0,0 @@
# 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

@ -6,7 +6,7 @@
#
from openram import debug
from openram.modules import bitcell_base
from openram.modules.bitcell_base import bitcell_base
from openram.tech import cell_properties as props
@ -23,6 +23,10 @@ class sky130_bitcell(bitcell_base):
cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1"
elif version == "opt1a":
cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1a"
elif version == "opt1_noblcon":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon"
elif version == "opt1a_noblcon":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon"
else:
debug.error("Invalid sky130 cell name", -1)

View File

@ -6,88 +6,84 @@
#
from openram import debug
from openram.modules import bitcell_array
from openram.modules.bitcell_array import bitcell_array
from openram.modules import pattern
from openram.sram_factory import factory
from openram.base import geometry
from openram import OPTS
from .sky130_bitcell_base_array import sky130_bitcell_base_array
from math import ceil
class sky130_bitcell_array(bitcell_array, sky130_bitcell_base_array):
"""
Creates a rows x cols array of memory cells.
Assumes bit-lines and word lines are connected by abutment.
"""
def __init__(self, rows, cols, column_offset=0, name=""):
# Don't call the regular bitcell_array constructor since we don't want its constructor, just
# some of it's useful member functions
sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name)
if self.row_size % 2 == 0:
debug.error("Invalid number of rows {}. number of rows (excluding dummy rows) must be odd to connect to col ends".format(self.row_size), -1)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, self.row_size, self.column_size))
self.add_comment("rows: {0} cols: {1}".format(self.row_size, self.column_size))
# This will create a default set of bitline/wordline names
self.create_all_bitline_names()
self.create_all_wordline_names()
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
self.add_supply_pins()
def __init__(self, rows, cols, column_offset=0, row_offset=0, name="", left_rbl=None, right_rbl=None):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, row_offset=row_offset, name=name)
self.left_rbl = left_rbl
self.right_rbl = right_rbl
self.column_offset = column_offset
self.row_offset = row_offset
def add_modules(self):
""" Add the modules used in this design """
# Bitcell for port names only
self.cell = factory.create(module_type=OPTS.bitcell, version="opt1")
self.cell2 = factory.create(module_type=OPTS.bitcell, version="opt1a")
self.cella = factory.create(module_type=OPTS.bitcell, version="opt1a")
#self.cell_noblcon = factory.create(module_type=OPTS.bitcell, version="opt1_noblcon")
#self.cella_noblcon = factory.create(module_type=OPTS.bitcell, version="opt1a_noblcon")
self.strap = factory.create(module_type="internal", version="wlstrap")
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.strap3 = factory.create(module_type="internal", version="wlstrapa")
self.strap4 = factory.create(module_type="internal", version="wlstrapa_p")
self.strap_p = factory.create(module_type="internal", version="wlstrap_p")
self.strapa = factory.create(module_type="internal", version="wlstrapa")
self.strapa_p = factory.create(module_type="internal", version="wlstrapa_p")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
self.array_layout = []
alternate_bitcell = (self.row_size) % 2
for row in range(0, self.row_size):
self.all_inst={}
self.cell_inst={}
#self.cell_noblcon_inst = geometry.instance("cell_noblcon_inst", mod=self.cell_noblcon, is_bitcell=True)
#self.cella_noblcon_inst = geometry.instance("cella_noblcon_inst", mod=self.cella_noblcon, is_bitcell=True)
row_layout = []
bit_row_opt1 = [geometry.instance("00_opt1", mod=self.cell, is_bitcell=True, mirror='MX')] \
+ [geometry.instance("01_strap_p", mod=self.strap, is_bitcell=False, mirror='MX')]\
+ [geometry.instance("02_opt1", mod=self.cell, is_bitcell=True, mirror='XY')] \
+ [geometry.instance("03_strap", mod=self.strap_p, is_bitcell=False, mirror='MX')]
bit_row_opt1a = [geometry.instance("10_opt1a", mod=self.cella, is_bitcell=True)] \
+ [geometry.instance("11_strapa", mod=self.strap, is_bitcell=False)] \
+ [geometry.instance("12_opt1a", mod=self.cella, is_bitcell=True, mirror='MY')] \
+ [geometry.instance("13_strapa_p", mod=self.strapa_p, is_bitcell=False)]
bit_block = []
if self.row_offset % 2 == 0:
pattern.append_row_to_block(bit_block, bit_row_opt1)
pattern.append_row_to_block(bit_block, bit_row_opt1a)
else:
pattern.append_row_to_block(bit_block, bit_row_opt1a)
pattern.append_row_to_block(bit_block, bit_row_opt1)
alternate_strap = (self.row_size+1) % 2
for col in range(0, self.column_size):
if alternate_bitcell == 1:
row_layout.append(self.cell)
self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col),
mod=self.cell)
else:
row_layout.append(self.cell2)
self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col),
mod=self.cell2)
self.connect_inst(self.get_bitcell_pins(row, col))
if col != self.column_size - 1:
if alternate_strap:
if row % 2:
name="row_{}_col_{}_wlstrapa_p".format(row, col)
row_layout.append(self.strap4)
self.add_inst(name=name, mod=self.strap4)
else:
name="row_{}_col_{}_wlstrap_p".format(row, col)
row_layout.append(self.strap2)
self.add_inst(name=name, mod=self.strap2)
alternate_strap = 0
else:
if row % 2:
name="row_{}_col_{}_wlstrapa".format(row, col)
row_layout.append(self.strap3)
self.add_inst(name=name.format(row, col), mod=self.strap3)
else:
name="row_{}_col_{}_wlstrap".format(row, col)
row_layout.append(self.strap)
self.add_inst(name=name.format(row, col), mod=self.strap)
alternate_strap = 1
self.connect_inst(self.get_strap_pins(row, col, name))
if alternate_bitcell == 0:
alternate_bitcell = 1
else:
alternate_bitcell = 0
self.array_layout.append(row_layout)
for row in bit_block:
row = pattern.rotate_list(row, self.column_offset * 2)
self.pattern = pattern(self, "bitcell_array", bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="bit_r{0}_c{1}")
self.pattern.connect_array()
# for i in range(len(self.insts)):
# if self.left_rbl:
# if "r{}".format(self.row_size-1) in self.insts[i].name:
# if self.insts[i].mod == self.cell:
# self.insts[i].mod = self.cell_noblcon_inst.mod
# self.insts[i].gds = self.cell_noblcon_inst.gds
# elif self.insts[i].mod == self.cella:
# self.insts[i].mod = self.cella_noblcon_inst.mod
# self.insts[i].gds = self.cella_noblcon_inst.gds
# if self.right_rbl:
# if "r{}".format("0") in self.insts[i].name:
# if self.insts[i].mod == self.cell:
# self.insts[i].mod = self.cell_noblcon_inst.mod
# self.insts[i].gds = self.cell_noblcon_inst.gds
# elif self.insts[i].mod == self.cella:
# self.insts[i].mod = self.cella_noblcon_inst.mod
# self.insts[i].gds = self.cella_noblcon_inst.gds

View File

@ -11,56 +11,18 @@ from openram.modules import bitcell_base_array
from openram.sram_factory import factory
from openram.tech import layer
from openram import OPTS
from openram.modules import pattern
class sky130_bitcell_base_array(bitcell_base_array):
"""
Abstract base class for bitcell-arrays -- bitcell, dummy, replica
"""
def __init__(self, name, rows, cols, column_offset):
super().__init__(name, rows, cols, column_offset)
def __init__(self, name, rows, cols, column_offset, row_offset):
super().__init__(name, rows, cols, column_offset, row_offset)
debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols))
self.cell = factory.create(module_type=OPTS.bitcell, version="opt1")
def place_array(self, name_template, row_offset=0, col_offset=0):
yoffset = 0.0
for row in range(0, len(self.array_layout)):
xoffset = 0.0
for col in range(0, len(self.array_layout[row])):
self.place_inst = self.insts[(col) + (row) * len(self.array_layout[row])]
if row % 2 == 0:
if col == 0:
self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX")
elif col % 4 == 0:
self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX")
elif col % 4 == 3 :
self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX")
elif col % 4 == 2:
self.place_inst.place(offset=[xoffset + self.cell.width, yoffset + self.cell.height], mirror="XY")
else:
self.place_inst.place(offset=[xoffset, yoffset + self.cell.height], mirror="MX")
else:
if col == 0:
self.place_inst.place(offset=[xoffset, yoffset])
elif col % 4 == 0:
self.place_inst.place(offset=[xoffset, yoffset])
elif col % 4 == 3 :
self.place_inst.place(offset=[xoffset, yoffset])
elif col % 4 == 2:
self.place_inst.place(offset=[xoffset + self.cell.width, yoffset], mirror="MY")
# self.place_inst.place(offset=[xoffset, yoffset])
else:
self.place_inst.place(offset=[xoffset, yoffset])
xoffset += self.place_inst.width
yoffset += self.place_inst.height
self.width = max([x.rx() for x in self.insts])
self.height = max([x.uy() for x in self.insts])
def get_bitcell_pins(self, row, col, swap = False):
"""
Creates a list of connections in the bitcell,
@ -123,66 +85,36 @@ class sky130_bitcell_base_array(bitcell_base_array):
strap_pins = ["vdd", "gnd", "vdd"]
return strap_pins
def add_supply_pins(self):
""" Add the layout pins """
def route_supplies(self):
# Copy a vdd/gnd layout pin from every cell
super().route_supplies()
for inst in self.insts:
if "wlstrap" in inst.name:
if "VPWR" in inst.mod.pins:
self.copy_layout_pin(inst, "VPWR", "vdd")
if "VGND" in inst.mod.pins:
self.copy_layout_pin(inst, "VGND", "gnd")
if "VPWR" in inst.mod.pins:
self.copy_layout_pin(inst, "VPWR", "vdd")
if "VGND" in inst.mod.pins:
self.copy_layout_pin(inst, "VGND", "gnd")
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
if hasattr(self, 'cell_inst'):
for col in range(self.column_size):
inst = self.cell_inst[0,col]
pin = inst.get_pin("vpb")
self.objs.append(geometry.rectangle(layer["nwell"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
try:
from openram.tech import layer_override
if layer_override['VNB']:
pin = inst.get_pin("vnb")
self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center()))
self.objs.append(geometry.rectangle(layer["pwellp"],
pin.ll(),
pin.width(),
pin.height()))
except:
pin = inst.get_pin("vnb")
self.add_label("vdd", pin.layer, pin.center())
if row == 2: #add only 1 label per col
if 'VPB' or 'vpb' in self.cell_inst[row, col].mod.pins:
pin = inst.get_pin("vpb")
self.objs.append(geometry.rectangle(layer["nwell"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
if 'VNB' or 'vnb'in self.cell_inst[row, col].mod.pins:
try:
from openram.tech import layer_override
if layer_override['VNB']:
pin = inst.get_pin("vnb")
self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center()))
self.objs.append(geometry.rectangle(layer["pwellp"],
pin.ll(),
pin.width(),
pin.height()))
except:
pin = inst.get_pin("vnb")
self.add_label("vdd", pin.layer, pin.center())
def add_bitline_pins(self):
bitline_names = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for port in self.all_ports:
bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port])
text = "bl_{0}_{1}".format(port, col)
#if "Y" in self.cell_inst[0, col].mirror:
# text = text.replace("bl", "br")
self.add_layout_pin(text=text,
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1])
text = "br_{0}_{1}".format(port, col)
#if "Y" in self.cell_inst[0, col].mirror:
# text = text.replace("br", "bl")
self.add_layout_pin(text=text,
layer=br_pin.layer,
offset=br_pin.ll().scale(1, 0),
width=br_pin.width(),
height=self.height)

View File

@ -7,213 +7,199 @@
from openram import debug
from openram.base import vector
from openram.base import contact
from openram import debug
from openram.base import round_to_grid
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.capped_replica_bitcell_array import capped_replica_bitcell_array
from .sky130_bitcell_base_array import sky130_bitcell_base_array
from math import sqrt
class sky130_capped_replica_bitcell_array(sky130_bitcell_base_array):
class sky130_capped_replica_bitcell_array(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))
super().__init__(rows, cols, rbl, left_rbl, right_rbl, name)
def route_power_ring(self, v_layer, h_layer):
# ring is manually added and routed in add_layout_pins
pass
# This is how many RBLs are in all the arrays
self.rbl = rbl
# This specifies which RBL to put on the left or right by port number
# This could be an empty list
if left_rbl is not None:
self.left_rbl = left_rbl
else:
self.left_rbl = []
# This could be an empty list
if right_rbl is not None:
self.right_rbl = right_rbl
else:
self.right_rbl = []
def add_layout_pins(self):
""" Add the layout pins """
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
for row_end in self.dummy_col_insts:
row_end = row_end.mod
print(self.get_all_wordline_names(), row_end.get_wordline_names())
for (rba_wl_name, wl_name) in zip(self.get_all_wordline_names(), row_end.get_wordline_names()):
pin = row_end.get_pin(wl_name)
self.add_layout_pin(text=rba_wl_name,
layer=pin.layer,
offset=vector(0,pin.ll().scale(0, 1)[1]),
#width=self.width,
width=pin.width(),
height=pin.height())
def create_netlist(self):
""" Create and connect the netlist """
self.add_modules()
self.add_pins()
self.create_instances()
pin_height = (round_to_grid(drc["minarea_m3"] / round_to_grid(sqrt(drc["minarea_m3"]))) + drc["{0}_to_{0}".format('m3')])
drc_width = drc["{0}_to_{0}".format('m3')]
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)
# vdd/gnd are only connected in the perimeter cells
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
supply_insts = self.dummy_row_insts
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()
for pin_name in self.supplies:
for supply_inst in supply_insts:
vdd_alternate = 0
gnd_alternate = 0
for cell_inst in supply_inst.mod.insts:
inst = cell_inst.mod
for pin in inst.get_pins(pin_name):
if pin.name == 'vdd':
if vdd_alternate:
connection_offset = -0.02
vdd_alternate = 0
else:
connection_offset = 0.02
vdd_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 1
elif pin.name == 'gnd':
if gnd_alternate:
connection_offset = 0.00
gnd_alternate = 0
else:
connection_offset = 0.00
gnd_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 4
pin_width = round_to_grid(sqrt(drc["minarea_m3"]))
pin_height = round_to_grid(drc["minarea_m3"] / pin_width)
if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent' or 'corner' in inst.cell_name:
if 'dummy_row_bot' in supply_inst.name:
pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'dummy_row_top' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width)
# elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX':
# pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2))
# self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
# elif 'replica_col' in supply_inst.name:
# pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2))
# self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width)
self.add_via_stack_center(from_layer=pin.layer,
to_layer='m2',
offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0))
def get_column_offsets(self):
return self.replica_bitcell_array.get_column_offsets()
# add well contacts to perimeter cells
for pin_name in ['vpb', 'vnb']:
for supply_inst in supply_insts:
vnb_alternate = 0
vpb_alternate = 0
for cell_inst in supply_inst.mod.insts:
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)
inst = cell_inst.mod
for pin in inst.get_pins(pin_name):
if pin.name == 'vpb':
if vpb_alternate:
connection_offset = 0.01
vpb_alternate = 0
else:
connection_offset = 0.02
vpb_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 2
elif pin.name == 'vnb':
if vnb_alternate:
connection_offset = -0.01
vnb_alternate = 0
else:
connection_offset = -0.02
vnb_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 3
if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent':
if 'dummy_row_bot' in supply_inst.name:
pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'dummy_row_top' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width)
# elif 'replica_col' in supply_inst.name:
# pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2))
# self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
# elif 'replica_col' in supply_inst.name:
# pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2))
# self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width)
self.add_via_stack_center(from_layer=pin.layer,
to_layer='m2',
offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0))
# Calculate the bitcell power which currently only includes leakage
cell_power = self.cell.analytical_power(corner, load)
min_area = drc["minarea_{}".format('m3')]
for track,supply, offset in zip(range(1,5),['vdd','vdd','gnd','gnd'],[min_area * 6,min_area * 6, 0, 0]):
y_offset = track * (pin_height + drc_width*2)
self.add_segment_center('m2', vector(-0.4,-y_offset), vector(self.width+0.4, -y_offset), drc["minwidth_{}".format('m2')])
self.add_segment_center('m2', vector(-0.4,self.height + y_offset), vector(self.width+0.4, self.height + y_offset), drc["minwidth_{}".format('m2')])
self.add_power_pin(name=supply,
loc=vector(round_to_grid(sqrt(min_area))/2 + offset, -y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(round_to_grid(sqrt(min_area))/2 + offset, self.height + y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, -y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, self.height + y_offset),
start_layer='m2')
# 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
self.offset_all_coordinates()
self.height = self.height + self.dummy_col_insts[0].lr().y * 2
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()
for pin_name in self.bitline_pin_list:
pin_list = self.replica_bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
if 'bl' in pin.name:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
elif 'br' in pin_name:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0) + vector(0,pin_height + drc_width*2),
width=pin.width(),
height=self.height - 2 *(pin_height + drc_width*2))
# # Replica bitlines
# if len(self.rbls) > 0:
# for (names, inst) in zip(self.rbl_bitline_names, self.replica_col_insts):
# pin_names = self.replica_bitcell_array_inst.mod.replica_columns[self.rbls[0]].all_bitline_names
# mirror = self.replica_col_insts[0].mirror
# for (bl_name, pin_name) in zip(names, pin_names):
# pin = inst.get_pin(pin_name)
# if 'rbl_bl' in bl_name:
# # if mirror != "MY":
# # bl_name = bl_name.replace("rbl_bl","rbl_br")
# self.add_layout_pin(text=bl_name,
# layer=pin.layer,
# offset=pin.ll().scale(1, 0),
# width=pin.width(),
# height=self.height)
# elif 'rbl_br' in bl_name:
# # if mirror != "MY":
# # bl_name = bl_name.replace("rbl_br","rbl_bl")
# self.add_layout_pin(text=bl_name,
# layer=pin.layer,
# offset=pin.ll().scale(1, 0) + vector(0,(pin_height + drc_width*2)),
# width=pin.width(),
# height=self.height - 2 *(pin_height + drc_width*2))
return

View File

@ -8,20 +8,21 @@
from openram import debug
from openram.base import design
from openram.tech import cell_properties as props
from openram import OPTS
class sky130_col_cap(design):
def __init__(self, version, name=""):
def __init__(self, version, name="",left_rbl=[],right_rbl=[]):
if version == "colend":
cell_name = "sky130_fd_bd_sram__sram_sp_colend"
prop = props.col_cap_1port_bitcell
elif version == "colenda":
cell_name = "sky130_fd_bd_sram__sram_sp_colenda"
prop = props.col_cap_1port_bitcell
elif version == "colend_p_cent":
cell_name = "sky130_fd_bd_sram__sram_sp_colend_p_cent"
prop = props.col_cap_1port_strap_ground
elif version == "colenda":
cell_name = "sky130_fd_bd_sram__sram_sp_colenda"
prop = props.col_cap_1port_bitcell
elif version == "colenda_p_cent":
cell_name = "sky130_fd_bd_sram__sram_sp_colenda_p_cent"
prop = props.col_cap_1port_strap_ground
@ -32,5 +33,5 @@ class sky130_col_cap(design):
cell_name = "sky130_fd_bd_sram__sram_sp_colenda_cent"
prop = props.col_cap_1port_strap_power
else:
debug.error("Invalid type for col_end", -1)
debug.error("Invalid type for col_end: {}".format(version), -1)
super().__init__(name=name, cell_name=cell_name, prop=prop)

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California
@ -9,247 +9,91 @@ from openram.base import geometry
from openram.sram_factory import factory
from openram.tech import layer
from openram import OPTS
from openram.modules.col_cap_array import col_cap_array
from .sky130_bitcell_base_array import sky130_bitcell_base_array
from openram.modules import pattern
from math import ceil
class sky130_col_cap_array(sky130_bitcell_base_array):
class sky130_col_cap_array(col_cap_array, sky130_bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, location, column_offset=0, mirror=0, name=""):
# Don't call the regular col-cap_array constructor since we don't want its constructor, just
# some of it's useful member functions
sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name)
self.mirror = mirror
self.location = location
self.rows = rows
self.cols = cols
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
# This module has no wordlines
# self.create_all_wordline_names()
# This module has no bitlines
# self.create_all_bitline_names()
self.add_modules()
self.create_all_wordline_names()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.add_layout_pins()
self.add_supply_pins()
self.add_boundary()
self.DRC_LVS()
def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name="", left_rbl=[],right_rbl=[]):
self.left_rbl = left_rbl
self.right_rbl = right_rbl
super().__init__(rows, cols, column_offset=column_offset, mirror=mirror, location=location, name=name, left_rbl=left_rbl, right_rbl=right_rbl)
self.no_instances = False
def add_modules(self):
""" Add the modules used in this design """
if self.location == "top":
self.colend1 = factory.create(module_type="col_cap", version="colend")
self.colend1 = factory.create(module_type="col_cap", version="colend",left_rbl=self.left_rbl, right_rbl=self.right_rbl)
self.colend2 = factory.create(module_type="col_cap", version="colend_p_cent")
self.colend3 = factory.create(module_type="col_cap", version="colend_cent")
elif self.location == "bottom":
self.colend1 = factory.create(module_type="col_cap", version="colenda")
self.colend1 = factory.create(module_type="col_cap", version="colenda",left_rbl=self.left_rbl, right_rbl=self.right_rbl)
self.colend2 = factory.create(module_type="col_cap", version="colenda_p_cent")
self.colend3 = factory.create(module_type="col_cap", version="colenda_cent")
self.cell = factory.create(module_type=OPTS.bitcell, version="opt1")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
self.array_layout = []
bitline = 0
for col in range((self.column_size * 2) - 1):
row_layout = []
name="rca_{0}_{1}".format(self.location, col)
# Top/bottom cell are always dummy cells.
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
pins = []
if col % 4 == 0:
row_layout.append(self.colend1)
self.cell_inst[col]=self.add_inst(name=name, mod=self.colend1)
pins.append("fake_bl_{}".format(bitline))
pins.append("fake_br_{}".format(bitline))
pins.append("vdd")
pins.append("gnd")
pins.append("vdd")
pins.append("gnd")
pins.append("gate")
bitline += 1
elif col % 4 == 1:
row_layout.append(self.colend2)
self.cell_inst[col]=self.add_inst(name=name, mod=self.colend3)
pins.append("vdd")
pins.append("vdd")
pins.append("gnd")
elif col % 4 == 2:
row_layout.append(self.colend1)
self.cell_inst[col]=self.add_inst(name=name, mod=self.colend1)
pins.append("fake_bl_{}".format(bitline))
pins.append("fake_br_{}".format(bitline))
pins.append("vdd")
pins.append("gnd")
pins.append("vdd")
pins.append("gnd")
pins.append("gate")
bitline += 1
elif col % 4 ==3:
row_layout.append(self.colend2)
self.cell_inst[col]=self.add_inst(name=name, mod=self.colend2)
pins.append("gnd")
pins.append("vdd")
pins.append("vnb")
self.all_inst={}
self.cell_inst={}
if self.location == "top":
bit_row = [geometry.instance("02_colend", mod=self.colend1, is_bitcell=True, mirror="MY")] \
+ [geometry.instance("03_strap_p", mod=self.colend3, is_bitcell=False)] \
+ [geometry.instance("00_colend", mod=self.colend1, is_bitcell=True)] \
+ [geometry.instance("01_strap_p_cent", mod=self.colend2, is_bitcell=False)]\
self.connect_inst(pins)
self.array_layout.append(row_layout)
def place_array(self, name_template, row_offset=0):
xoffset = 0.0
yoffset = 0.0
for col in range(len(self.insts)):
inst = self.insts[col]
if col % 4 == 0:
inst.place(offset=[xoffset + inst.width, yoffset], mirror="MY")
elif col % 4 == 1:
inst.place(offset=[xoffset, yoffset])
elif col % 4 == 2:
inst.place(offset=[xoffset, yoffset])
elif col % 4 ==3:
inst.place(offset=[xoffset, yoffset])
xoffset += inst.width
self.width = max([x.rx() for x in self.insts])
self.height = max([x.uy() for x in self.insts])
def add_pins(self):
for fake_bl in range(self.cols):
self.add_pin("fake_bl_{}".format(fake_bl), "OUTPUT")
self.add_pin("fake_br_{}".format(fake_bl), "OUTPUT")
#self.add_pin("fake_wl", "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
self.add_pin("gate", "BIAS")
elif self.location == "bottom":
bit_row = [geometry.instance("02_colend", mod=self.colend1, is_bitcell=True, mirror="XY")] \
+ [geometry.instance("03_strap_p", mod=self.colend3, is_bitcell=False, mirror="MX")] \
+ [geometry.instance("00_colend", mod=self.colend1, is_bitcell=True, mirror="MX")] \
+ [geometry.instance("01_strap_p_cent", mod=self.colend2, is_bitcell=False, mirror="MX")]\
def add_layout_pins(self):
""" Add the layout pins """
# Add vdd/gnd via stacks
for cols in range((self.column_size * 2) - 1):
inst = self.cell_inst[cols]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
if inst.mod.cell_name == 'sky130_fd_bd_sram__sram_sp_colend' or 'sky130_fd_bd_sram__sram_sp_colenda':
if inst.mirror == "MY":
if pin_name == "vdd" and pin.layer == 'm1':
self.add_layout_pin_rect_center(text="vdd",
layer=pin.layer,
offset=inst.lr(),
width=pin.width(),
height=pin.height())
elif pin_name == "gnd" and pin.layer == 'm1':
self.add_layout_pin_rect_center(text="gnd",
layer=pin.layer,
offset=inst.ll(),
width=pin.width(),
height=pin.height())
else:
if pin_name == "vdd" and pin.layer == 'm1':
self.add_layout_pin_rect_center(text="vdd",
layer=pin.layer,
offset=inst.ll(),
width=pin.width(),
height=pin.height())
elif pin_name == "gnd" and pin.layer == 'm1':
self.add_layout_pin_rect_center(text="gnd",
layer=pin.layer,
offset=inst.lr(),
width=pin.width(),
height=pin.height())
bit_row = pattern.rotate_list(bit_row, self.column_offset * 2)
bit_block = []
pattern.append_row_to_block(bit_block, bit_row)
self.pattern = pattern(self, "col_cap_array_" + self.location , bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="col_cap_array" + self.location + "_r{0}_c{1}")
self.pattern.connect_array()
def get_bitcell_pins(self, row, col):
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""
bitcell_pins = []
for port in self.all_ports:
bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))])
bitcell_pins.append("vdd") # vdd
bitcell_pins.append("gnd") # gnd
bitcell_pins.append("vdd") # vpb
bitcell_pins.append("gnd") # vnb
bitcell_pins.append("gnd")# poly gate for parasitic tx
#bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
for col in range(len(self.insts)):
inst = self.insts[col]
if col % 4 == 0:
pin = self.cell_inst[col].get_pin("bl")
text = "fake_bl_{}".format(int(col/2))
self.add_layout_pin(text=text,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=pin.height())
pin = self.cell_inst[col].get_pin("br")
text = "fake_br_{}".format(int(col/2))
self.add_layout_pin(text=text,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=pin.height())
elif col % 4 == 2:
pin = self.cell_inst[col].get_pin("bl")
text = "fake_bl_{}".format(int(col/2))
self.add_layout_pin(text=text,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=pin.height())
pin = self.cell_inst[col].get_pin("br")
text = "fake_br_{}".format(int(col/2))
self.add_layout_pin(text=text,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=pin.height())
return
return bitcell_pins
def add_supply_pins(self):
for col in range(len(self.insts)):
inst = self.cell_inst[col]
def get_strap_pins(self, row, col):
strap_pins = []
if col % 2 == 0 and col % 4 != 0:
strap_pins.append("vdd") # vdd
else:
strap_pins.append("gnd") # gnd
strap_pins.append("vdd") # vpb
strap_pins.append("gnd") # vnb
return strap_pins
def create_layout(self):
if 'VPB' or 'vnb' in self.cell_inst[col].mod.pins:
pin = inst.get_pin("vpb")
self.objs.append(geometry.rectangle(layer["nwell"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
self.place_array()
self.add_layout_pins()
if 'VNB' or 'vnb' in self.cell_inst[col].mod.pins:
try:
from openram.tech import layer_override
if layer_override['VNB']:
pin = inst.get_pin("vnb")
self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center()))
self.objs.append(geometry.rectangle(layer["pwellp"],
pin.ll(),
pin.width(),
pin.height()))
except:
pin = inst.get_pin("vnb")
self.add_label("vdd", pin.layer, pin.center())
def create_all_wordline_names(self, row_size=None):
if row_size == None:
row_size = self.row_size
for row in range(row_size):
for port in self.all_ports:
self.wordline_names[port].append("wl_{0}_{1}".format(port, row))
self.all_wordline_names = [x for sl in zip(*self.wordline_names) for x in sl]
self.add_boundary()
self.DRC_LVS()

View File

@ -7,27 +7,22 @@
from openram import debug
from openram.base import design
from openram.base import get_libcell_size
from openram.tech import layer, GDS
from openram.tech import cell_properties as props
class sky130_corner(design):
def __init__(self, location, name=""):
super().__init__(name)
if location == "ul":
self.name = "sky130_fd_bd_sram__sram_sp_corner"
cell_name = "sky130_fd_bd_sram__sram_sp_corner"
elif location == "ur":
self.name = "sky130_fd_bd_sram__sram_sp_cornerb"
cell_name = "sky130_fd_bd_sram__sram_sp_cornerb"
elif location == "ll":
self.name = "sky130_fd_bd_sram__sram_sp_cornera"
cell_name = "sky130_fd_bd_sram__sram_sp_cornera"
elif location == "lr":
self.name = "sky130_fd_bd_sram__sram_sp_cornera"
cell_name = "sky130_fd_bd_sram__sram_sp_cornera"
else:
debug.error("Invalid sky130_corner location", -1)
design.__init__(self, name=self.name)
(self.width, self.height) = get_libcell_size(self.name,
GDS["unit"],
layer["mem"])
# pin_map = get_libcell_pins(pin_names, self.name, GDS["unit"])
super().__init__(name=name, cell_name=cell_name, prop=props.col_cap_1port_strap_power)
self.no_instances = True

View File

@ -10,186 +10,61 @@ from openram.sram_factory import factory
from openram.tech import layer
from openram import OPTS
from .sky130_bitcell_base_array import sky130_bitcell_base_array
from openram.modules import dummy_array
from openram.modules import pattern
from math import ceil
class sky130_dummy_array(sky130_bitcell_base_array):
class sky130_dummy_array(dummy_array, sky130_bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, column_offset=0, row_offset=0 ,mirror=0, location="", name=""):
super().__init__(rows=rows, cols=cols, column_offset=column_offset, name=name)
self.mirror = mirror
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
# This will create a default set of bitline/wordline names
self.create_all_bitline_names()
self.create_all_wordline_names()
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.add_layout_pins()
self.add_supply_pins()
self.add_boundary()
self.DRC_LVS()
def add_modules(self):
""" Add the modules used in this design """
self.dummy_cell = factory.create(module_type=OPTS.dummy_bitcell, version="opt1")
self.dummy_cell2 = factory.create(module_type=OPTS.dummy_bitcell, version="opt1a")
self.dummy_cella = factory.create(module_type=OPTS.dummy_bitcell, version="opt1a")
self.strap = factory.create(module_type="internal", version="wlstrap")
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.strap3 = factory.create(module_type="internal", version="wlstrapa")
self.strap4 = factory.create(module_type="internal", version="wlstrapa_p")
self.strap_p = factory.create(module_type="internal", version="wlstrap_p")
self.strapa = factory.create(module_type="internal", version="wlstrapa")
self.strapa_p = factory.create(module_type="internal", version="wlstrapa_p")
self.cell = factory.create(module_type=OPTS.bitcell, version="opt1")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
self.array_layout = []
alternate_bitcell = (self.row_size + 1) % 2
for row in range(0, self.row_size):
# this code needs to use connect_array_raw() to make dummy columns correctly, but single port shouldn't need these since there are dedicated cap cells
self.all_inst={}
self.cell_inst={}
bit_row_opt1 = [geometry.instance("00_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='MX')] \
+ [geometry.instance("01_strap", mod=self.strap, is_bitcell=False, mirror='MX')]\
+ [geometry.instance("02_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='XY')] \
+ [geometry.instance("03_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]
bit_row_opt1a = [geometry.instance("10_opt1a", mod=self.dummy_cella, is_bitcell=True, mirror='')] \
+ [geometry.instance("11_strapa", mod=self.strapa, is_bitcell=False, mirror='')] \
+ [geometry.instance("12_opt1a", mod=self.dummy_cella, is_bitcell=True, mirror='MY')] \
+ [geometry.instance("13_strapa_p", mod=self.strapa_p, is_bitcell=False, mirror='')]
row_layout = []
alternate_strap = (self.row_size + 1) % 2
for col in range(0, self.column_size):
if alternate_bitcell == 1:
row_layout.append(self.dummy_cell)
self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col),
mod=self.dummy_cell)
else:
row_layout.append(self.dummy_cell2)
self.cell_inst[row, col]=self.add_inst(name="row_{}_col_{}_bitcell".format(row, col),
mod=self.dummy_cell2)
self.connect_inst(self.get_bitcell_pins(row, col))
if col != self.column_size - 1:
if alternate_strap:
if col % 2:
name="row_{}_col_{}_wlstrap_p".format(row, col)
row_layout.append(self.strap4)
self.add_inst(name=name,
mod=self.strap4)
else:
name="row_{}_col_{}_wlstrapa_p".format(row, col)
row_layout.append(self.strap2)
self.add_inst(name=name,
mod=self.strap2)
alternate_strap = 0
else:
if col % 2:
name="row_{}_col_{}_wlstrap".format(row, col)
row_layout.append(self.strap)
self.add_inst(name=name,
mod=self.strap)
else:
name="row_{}_col_{}_wlstrapa".format(row, col)
row_layout.append(self.strap3)
self.add_inst(name=name,
mod=self.strap3)
alternate_strap = 1
self.connect_inst(self.get_strap_pins(row, col, name))
if alternate_bitcell == 0:
alternate_bitcell = 1
bit_block = []
if(self.row_offset % 2 == 0):
next_row = 1
else:
next_row = 0
for i in range(self.row_size):
if next_row == 0:
pattern.append_row_to_block(bit_block, bit_row_opt1)
next_row = 1
else:
alternate_bitcell = 0
self.array_layout.append(row_layout)
pattern.append_row_to_block(bit_block, bit_row_opt1a)
next_row = 0
def add_pins(self):
# bitline pins are not added because they are floating
for bl in range(self.column_size):
self.add_pin("bl_0_{}".format(bl))
self.add_pin("br_0_{}".format(bl))
for wl_name in self.get_wordline_names():
self.add_pin(wl_name, "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
#self.add_pin("vpb", "BIAS")
#Sself.add_pin("vnb", "BIAS")
for row in bit_block:
row = pattern.rotate_list(row, self.column_offset * 2)
def add_layout_pins(self):
""" Add the layout pins """
bitline_names = self.cell.get_all_bitline_names()
for col in range(self.column_size):
for port in self.all_ports:
bl_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port])
text = "bl_{0}_{1}".format(port, col)
self.add_layout_pin(text=text,
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
br_pin = self.cell_inst[0, col].get_pin(bitline_names[2 * port + 1])
text = "br_{0}_{1}".format(port, col)
self.add_layout_pin(text=text,
layer=br_pin.layer,
offset=br_pin.ll().scale(1, 0),
width=br_pin.width(),
height=self.height)
# self.add_rect(layer=bl_pin.layer,
# offset=bl_pin.ll().scale(1, 0),
# width=bl_pin.width(),
# height=self.height)
# self.add_rect(layer=br_pin.layer,
# offset=br_pin.ll().scale(1, 0),
# width=br_pin.width(),
# height=self.height)
wl_names = self.cell.get_all_wl_names()
for row in range(self.row_size):
for port in self.all_ports:
wl_pin = self.cell_inst[row, 0].get_pin(wl_names[port])
self.add_layout_pin(text="wl_{0}_{1}".format(port, row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
# Copy a vdd/gnd layout pin from every cell
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
def add_supply_pins(self):
for row in range(self.row_size):
for col in range(self.column_size):
inst = self.cell_inst[row, col]
if 'VPB' or 'vpb' in self.cell_inst[row, col].mod.pins:
pin = inst.get_pin("vpb")
self.objs.append(geometry.rectangle(layer["nwell"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
if 'VNB' or 'vnb' in self.cell_inst[row, col].mod.pins:
try:
from openram.tech import layer_override
if layer_override['VNB']:
pin = inst.get_pin("vnb")
self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center()))
self.objs.append(geometry.rectangle(layer["pwellp"],
pin.ll(),
pin.width(),
pin.height()))
except:
pin = inst.get_pin("vnb")
self.add_label("vdd", pin.layer, pin.center())
def input_load(self):
# FIXME: This appears to be old code from previous characterization. Needs to be updated.
wl_wire = self.gen_wl_wire()
return wl_wire.return_input_cap()
self.pattern = pattern(self, "bitcell_array", bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="dummy_bit_r{0}_c{1}")
self.pattern.connect_array()

View File

@ -21,9 +21,9 @@ class sky130_dummy_bitcell(bitcell_base):
# Ignore the name argument
if version == "opt1":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_dummy"
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon"
elif version == "opt1a":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy"
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon"
super().__init__(name, cell_name, prop=props.bitcell_1port)
debug.info(2, "Create dummy bitcell")

View File

@ -13,7 +13,7 @@ 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 openram.modules.replica_bitcell_array import replica_bitcell_array
from .sky130_bitcell_base_array import sky130_bitcell_base_array
@ -26,390 +26,7 @@ class sky130_replica_bitcell_array(replica_bitcell_array, sky130_bitcell_base_ar
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=""):
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
self.all_ports = list(range(total_ports))
def __init__(self, rows=0, cols=0, rbl=None, left_rbl=None, right_rbl=None, column_offset=0, row_offset=0, name="",):
debug.check((cols+ sum(rbl)) % 2==0, "must have an even number of cols including replica cols; you can add a spare col to fix this")
super().__init__(rows, cols, rbl, left_rbl, right_rbl, column_offset, row_offset, name)
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
if ((self.column_size + self.rbl[0] + self.rbl[1]) % array_col_multiple != 0):
debug.error("Invalid number of cols including rbl(s): {}. Total cols must be divisible by {}".format(self.column_size + self.rbl[0] + self.rbl[1], array_col_multiple), -1)
if ((self.row_size + self.rbl[0] + self.rbl[1]) % array_row_multiple != 0):
debug.error("invalid number of rows including dummy row(s): {}. Total cols must be divisible by {}".format(self.row_size + self.rbl[0] + self.rbl[1], array_row_multiple), -15)
super().__init__(self.row_size, self.column_size, rbl, left_rbl, right_rbl, name)
def create_layout(self):
# We will need unused wordlines grounded, so we need to know their layer
# and create a space on the left and right for the vias to connect to ground
pin = self.cell.get_pin(self.cell.get_all_wl_names()[0])
pin_layer = pin.layer
self.unused_pitch = 1.5 * getattr(self, "{}_pitch".format(pin_layer))
self.unused_offset = vector(self.unused_pitch, 0)
# This is a bitcell x bitcell offset to scale
self.bitcell_offset = vector(self.cell.width, self.cell.height)
self.strap_offset = vector(self.replica_col_insts[0].mod.strap1.width, self.replica_col_insts[0].mod.strap1.height)
self.col_end_offset = vector(self.dummy_row_insts[0].mod.colend1.width, self.dummy_row_insts[0].mod.colend1.height)
self.row_end_offset = vector(self.dummy_col_insts[0].mod.rowend1.width, self.dummy_col_insts[0].mod.rowend1.height)
# Everything is computed with the main array at (self.unused_pitch, 0) to start
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
self.offset_all_coordinates()
# Add extra width on the left and right for the unused WLs
#self.width = self.dummy_col_insts[0].rx() + self.unused_offset[0]
self.width = self.dummy_col_insts[1].rx()
self.height = self.dummy_col_insts[0].uy()
self.add_layout_pins()
self.route_unused_wordlines()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
super().add_pins()
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_array_inst.ll() \
- vector(0, self.col_cap_bottom.height) \
- vector(0, self.dummy_row.height) \
- vector(self.replica_columns[0].width, 0)
self.replica_col_insts[bit].place(offset + vector(0, self.replica_col_insts[bit].height), mirror="MX")
# 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] - (self.col_end_offset.y / self.cell.height)) \
+ self.strap_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 bit % 2 else "R0")
def add_end_caps(self):
""" Add dummy cells or end caps around the array """
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1]) + self.bitcell_array_inst.ul()
self.dummy_row_insts[1].place(offset=dummy_row_offset)
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) + self.unused_offset
self.dummy_row_insts[0].place(offset=dummy_row_offset + vector(0, self.dummy_row_insts[0].height), mirror="MX")
# 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.right_rbl) * (1 + self.strap_offset.x / self.cell.width), -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) - vector(self.replica_col_insts[0].width, 0) + self.unused_offset
self.dummy_col_insts[0].place(offset=dummy_col_offset, mirror="MY")
# 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) * (1 + self.strap_offset.x / self.cell.width), -self.rbl[0] - (self.col_end_offset.y / self.cell.height)) + self.bitcell_array_inst.lr()
self.dummy_col_insts[1].place(offset=dummy_col_offset)
def route_unused_wordlines(self):
""" Connect the unused RBL and dummy wordlines to gnd """
return
# This grounds all the dummy row word lines
for inst in self.dummy_row_insts:
for wl_name in self.col_cap.get_wordline_names():
self.ground_pin(inst, wl_name)
# 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:
self.ground_pin(inst, pin_name)
def add_layout_pins(self):
""" Add the layout pins """
for row_end in self.dummy_col_insts:
row_end = row_end.mod
for (rba_wl_name, wl_name) in zip(self.get_all_wordline_names(), row_end.get_wordline_names()):
pin = row_end.get_pin(wl_name)
self.add_layout_pin(text=rba_wl_name,
layer=pin.layer,
offset=vector(0,pin.ll().scale(0, 1)[1]),
#width=self.width,
width=pin.width(),
height=pin.height())
pin_height = (round_to_grid(drc["minarea_m3"] / round_to_grid(sqrt(drc["minarea_m3"]))) + drc["{0}_to_{0}".format('m3')])
drc_width = drc["{0}_to_{0}".format('m3')]
# vdd/gnd are only connected in the perimeter cells
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
supply_insts = self.dummy_row_insts + self.replica_col_insts
for pin_name in self.supplies:
for supply_inst in supply_insts:
vdd_alternate = 0
gnd_alternate = 0
for cell_inst in supply_inst.mod.insts:
inst = cell_inst.mod
for pin in inst.get_pins(pin_name):
if pin.name == 'vdd':
if vdd_alternate:
connection_offset = 0.035
vdd_alternate = 0
else:
connection_offset = -0.035
vdd_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 1
elif pin.name == 'gnd':
if gnd_alternate:
connection_offset = 0.035
gnd_alternate = 0
else:
connection_offset = -0.035
gnd_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 4
pin_width = round_to_grid(sqrt(drc["minarea_m3"]))
pin_height = round_to_grid(drc["minarea_m3"] / pin_width)
if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent' or 'corner' in inst.cell_name:
if 'dummy_row' in supply_inst.name and supply_inst.mirror == 'MX':
pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'dummy_row' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width)
elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX':
pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'replica_col' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width)
self.add_via_stack_center(from_layer=pin.layer,
to_layer='m2',
offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0))
# add well contacts to perimeter cells
for pin_name in ['vpb', 'vnb']:
for supply_inst in supply_insts:
vnb_alternate = 0
vpb_alternate = 0
for cell_inst in supply_inst.mod.insts:
inst = cell_inst.mod
for pin in inst.get_pins(pin_name):
if pin.name == 'vpb':
if vpb_alternate:
connection_offset = 0.01
vpb_alternate = 0
else:
connection_offset = 0.02
vpb_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 2
elif pin.name == 'vnb':
if vnb_alternate:
connection_offset = -0.01
vnb_alternate = 0
else:
connection_offset = -0.02
vnb_alternate = 1
connection_width = drc["minwidth_{}".format('m1')]
track_offset = 3
if inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_p_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colend_cent' or inst.cell_name == 'sky130_fd_bd_sram__sram_sp_colenda_cent':
if 'dummy_row' in supply_inst.name and supply_inst.mirror == 'MX':
pin_center = vector(pin.center()[0], -1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'dummy_row' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, self.height), connection_width)
elif 'replica_col' in supply_inst.name and cell_inst.mirror == 'MX':
pin_center = vector(pin.center()[0], -1 * track_offset* (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset, 0), connection_width)
elif 'replica_col' in supply_inst.name:
pin_center = vector(pin.center()[0],inst.height + 1 * track_offset * (pin_height + drc_width*2))
self.add_segment_center(pin.layer, pin_center+supply_inst.ll()+cell_inst.ll()+vector(connection_offset,0), vector((pin_center+supply_inst.ll()+cell_inst.ll())[0] + connection_offset,self.height), connection_width)
self.add_via_stack_center(from_layer=pin.layer,
to_layer='m2',
offset=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0))
min_area = drc["minarea_{}".format('m3')]
for track,supply, offset in zip(range(1,5),['vdd','vdd','gnd','gnd'],[min_area * 6,min_area * 6, 0, 0]):
y_offset = track * (pin_height + drc_width*2)
self.add_segment_center('m2', vector(0,-y_offset), vector(self.width, -y_offset), drc["minwidth_{}".format('m2')])
self.add_segment_center('m2', vector(0,self.height + y_offset), vector(self.width, self.height + y_offset), drc["minwidth_{}".format('m2')])
self.add_power_pin(name=supply,
loc=vector(round_to_grid(sqrt(min_area))/2 + offset, -y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(round_to_grid(sqrt(min_area))/2 + offset, self.height + y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, -y_offset),
start_layer='m2')
self.add_power_pin(name=supply,
loc=vector(self.width - round_to_grid(sqrt(min_area))/2 - offset, self.height + y_offset),
start_layer='m2')
self.offset_all_coordinates()
self.height = self.height + self.dummy_col_insts[0].lr().y * 2
for pin_name in self.all_bitline_names:
pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list:
if 'bl' in pin.name:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
elif 'br' in pin_name:
self.add_layout_pin(text=pin_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0) + vector(0,pin_height + drc_width*2),
width=pin.width(),
height=self.height - 2 *(pin_height + drc_width*2))
# 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
mirror = self.replica_col_insts[0].mirror
for (bl_name, pin_name) in zip(names, pin_names):
pin = inst.get_pin(pin_name)
if 'rbl_bl' in bl_name:
# if mirror != "MY":
# bl_name = bl_name.replace("rbl_bl","rbl_br")
self.add_layout_pin(text=bl_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0),
width=pin.width(),
height=self.height)
elif 'rbl_br' in bl_name:
# if mirror != "MY":
# bl_name = bl_name.replace("rbl_br","rbl_bl")
self.add_layout_pin(text=bl_name,
layer=pin.layer,
offset=pin.ll().scale(1, 0) + vector(0,(pin_height + drc_width*2)),
width=pin.width(),
height=self.height - 2 *(pin_height + drc_width*2))
return
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 + ["gnd"] + ["gnd"])
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 + self.supplies + ["gnd"])
self.dummy_row_insts.append(self.add_inst(name="dummy_row_top",
mod=self.col_cap_top))
self.connect_inst(self.all_bitline_names + self.supplies + ["gnd"])
# 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] + ["gnd"] + self.replica_array_wordline_names + ["gnd"] + 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] + ["gnd"] + self.replica_array_wordline_names + ["gnd"] + self.supplies)

View File

@ -11,9 +11,9 @@ from openram.sram_factory import factory
from openram.tech import layer
from openram import OPTS
from .sky130_bitcell_base_array import sky130_bitcell_base_array
class sky130_replica_column(sky130_bitcell_base_array):
from openram.modules import pattern
from openram.modules import replica_column
class sky130_replica_column(replica_column, sky130_bitcell_base_array):
"""
Generate a replica bitline column for the replica array.
Rows is the total number of rows i the main array.
@ -23,75 +23,7 @@ class sky130_replica_column(sky130_bitcell_base_array):
"""
def __init__(self, name, rows, rbl, replica_bit, column_offset=0):
# Used for pin names and properties
self.cell = factory.create(module_type=OPTS.bitcell)
# Row size is the number of rows with word lines
self.row_size = sum(rbl) + rows
# Start of regular word line rows
self.row_start = rbl[0] + 1
# End of regular word line rows
self.row_end = self.row_start + rows
if not self.cell.end_caps:
self.row_size += 2
super().__init__(rows=self.row_size, cols=1, column_offset=column_offset, name=name)
self.rows = rows
self.left_rbl = rbl[0]
self.right_rbl = rbl[1]
self.replica_bit = replica_bit
# left, right, regular rows plus top/bottom dummy cells
self.total_size = self.left_rbl + rows + self.right_rbl + 2
self.column_offset = column_offset
if self.rows % 2 == 0:
debug.error("Invalid number of rows {}. Number of rows must be even to connect to col ends".format(self.rows), -1)
if self.column_offset % 2 == 0:
debug.error("Invalid column_offset {}. Column offset must be odd to connect to col ends".format(self.rows), -1)
debug.check(replica_bit != 0 and replica_bit != rows,
"Replica bit cannot be the dummy row.")
debug.check(replica_bit <= self.left_rbl or replica_bit >= self.total_size - self.right_rbl - 1,
"Replica bit cannot be in the regular array.")
# if OPTS.tech_name == "sky130":
# debug.check(rows % 2 == 0 and (self.left_rbl + 1) % 2 == 0,
# "sky130 currently requires rows to be even and to start with X mirroring"
# + " (left_rbl must be even) for LVS.")
# commented out to support odd row counts while testing opc
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_instances()
self.width = max([x.rx() for x in self.insts])
self.height = max([x.uy() for x in self.insts])
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.create_all_bitline_names()
#self.create_all_wordline_names(self.row_size+2)
# +2 to add fake wl pins for colends
self.create_all_wordline_names(self.row_size+1, 1)
self.add_pin_list(self.all_bitline_names, "OUTPUT")
self.add_pin_list(self.all_wordline_names, "INPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
self.add_pin("top_gate", "INPUT")
self.add_pin("bot_gate", "INPUT")
super().__init__(name=name, rows=rows, rbl=rbl, replica_bit=replica_bit, column_offset=column_offset)
def add_modules(self):
self.replica_cell = factory.create(module_type="replica_bitcell_1port", version="opt1")
@ -99,172 +31,65 @@ class sky130_replica_column(sky130_bitcell_base_array):
self.replica_cell2 = factory.create(module_type="replica_bitcell_1port", version="opt1a")
self.dummy_cell = factory.create(module_type="dummy_bitcell_1port", version="opt1")
self.dummy_cell2 = factory.create(module_type="dummy_bitcell_1port", version="opt1")
self.strap1 = factory.create(module_type="internal", version="wlstrap")
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.strap3 = factory.create(module_type="internal", version="wlstrapa_p")
self.colend = factory.create(module_type="col_cap", version="colend")
self.edge_cell = self.colend
self.colenda = factory.create(module_type="col_cap", version="colenda")
self.colend_p_cent = factory.create(module_type="col_cap", version="colend_p_cent")
self.colenda_p_cent = factory.create(module_type="col_cap", version="colenda_p_cent")
self.dummy_cell2 = factory.create(module_type="dummy_bitcell_1port", version="opt1a")
self.strap = factory.create(module_type="internal", version="wlstrap")
self.strap_p = factory.create(module_type="internal", version="wlstrap_p")
self.strapa = factory.create(module_type="internal", version="wlstrapa")
self.strapa_p = factory.create(module_type="internal", version="wlstrapa_p")
def create_instances(self):
self.cell_inst = {}
self.array_layout = []
alternate_bitcell = (self.rows + 1) % 2
""" Create the module instances used in this design """
self.all_inst={}
self.cell_inst={}
replica_row_opt1 = [geometry.instance("rep_00_opt1", mod=self.replica_cell, is_bitcell=True, mirror='XY')] \
+ [geometry.instance("rep_01_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]\
+ [geometry.instance("rep_02_opt1", mod=self.replica_cell, is_bitcell=True, mirror='MX')] \
+ [geometry.instance("rep_03_strap", mod=self.strap, is_bitcell=False, mirror='MX')]
replica_row_opt1a = [geometry.instance("rep_10_opt1a", mod=self.replica_cell2, is_bitcell=True, mirror='MY')] \
+ [geometry.instance("rep_11_strap_p", mod=self.strap_p, is_bitcell=False)] \
+ [geometry.instance("rep_12_opt1a", mod=self.replica_cell2, is_bitcell=True)] \
+ [geometry.instance("rep_13_strapaa", mod=self.strapa, is_bitcell=False)]
dummy_row_opt1 = [geometry.instance("dummy_00_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='XY')] \
+ [geometry.instance("dummy_01_strap_p", mod=self.strap_p, is_bitcell=False, mirror='MX')]\
+ [geometry.instance("dummy_02_opt1", mod=self.dummy_cell, is_bitcell=True, mirror='MX')] \
+ [geometry.instance("dummy_03_strap", mod=self.strap, is_bitcell=False, mirror='MX')]
dummy_row_opt1a = [geometry.instance("dummy_10_opt1a", mod=self.dummy_cell2, is_bitcell=True, mirror='MY')] \
+ [geometry.instance("dummy_11_strap_p", mod=self.strap_p, is_bitcell=False)] \
+ [geometry.instance("dummy_12_opt1a", mod=self.dummy_cell2, is_bitcell=True)] \
+ [geometry.instance("dummy_13_strapa", mod=self.strapa, is_bitcell=False)]
bit_block = []
if self.column_offset % 2 == 1:
replica_row_opt1 = replica_row_opt1[0:2]
replica_row_opt1a = replica_row_opt1a[0:2]
dummy_row_opt1 = dummy_row_opt1[0:2]
dummy_row_opt1a = dummy_row_opt1a[0:2]
else:
replica_row_opt1 = replica_row_opt1[2:4]
replica_row_opt1a = replica_row_opt1a[2:4]
dummy_row_opt1 = dummy_row_opt1[2:4]
dummy_row_opt1a = dummy_row_opt1a[2:4]
current_row = self.row_start
for row in range(self.total_size):
row_layout = []
name="rbc_{0}".format(row)
# Top/bottom cell are always dummy cells.
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
if (row > self.left_rbl and row < self.total_size - 1 or row == self.replica_bit):
if alternate_bitcell == 0:
row_layout.append(self.replica_cell)
self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell)
self.connect_inst(self.get_bitcell_pins(row, 0))
row_layout.append(self.strap2)
self.add_inst(name=name + "_strap_p", mod=self.strap2)
self.connect_inst(self.get_strap_pins(row, 0, name + "_strap_p"))
alternate_bitcell = 1
# Regular array cells are replica cells
# Replic bit specifies which other bit (in the full range (0,total_size) to make a replica cell.
# All other cells are dummies
if (row == self.replica_bit) or (row >= self.row_start and row < self.row_end):
if current_row % 2 == 0:
pattern.append_row_to_block(bit_block, replica_row_opt1)
else:
row_layout.append(self.replica_cell2)
self.cell_inst[row]=self.add_inst(name=name, mod=self.replica_cell2)
self.connect_inst(self.get_bitcell_pins(row, 0))
row_layout.append(self.strap3)
self.add_inst(name=name + "_strap", mod=self.strap3)
self.connect_inst(self.get_strap_pins(row, 0))
alternate_bitcell = 0
elif (row == 0):
row_layout.append(self.colend)
self.cell_inst[row]=self.add_inst(name=name, mod=self.colend)
self.connect_inst(self.get_col_cap_pins(row, 0))
row_layout.append(self.colend_p_cent)
self.add_inst(name=name + "_cap", mod=self.colend_p_cent)
self.connect_inst(self.get_col_cap_p_pins(row, 0))
elif (row == self.total_size - 1):
row_layout.append(self.colenda)
self.cell_inst[row]=self.add_inst(name=name, mod=self.colenda)
self.connect_inst(self.get_col_cap_pins(row, 0))
row_layout.append(self.colenda_p_cent)
self.add_inst(name=name + "_cap", mod=self.colenda_p_cent)
self.connect_inst(self.get_col_cap_p_pins(row, 0))
self.array_layout.append(row_layout)
def place_instances(self, name_template="", row_offset=0):
col_offset = self.column_offset
yoffset = 0.0
for row in range(row_offset, len(self.array_layout) + row_offset):
xoffset = 0.0
for col in range(col_offset, len(self.array_layout[row]) + col_offset):
self.place_inst = self.insts[(col - col_offset) + (row - row_offset) * len(self.array_layout[row - row_offset])]
if row == row_offset or row == (len(self.array_layout) + row_offset -1):
if row == row_offset:
self.place_inst.place(offset=[xoffset, yoffset + self.colend.height], mirror="MX")
else:
self.place_inst.place(offset=[xoffset, yoffset])
elif col % 2 == 0:
if row % 2 == 0:
self.place_inst.place(offset=[xoffset, yoffset + self.place_inst.height], mirror="MX")
else:
self.place_inst.place(offset=[xoffset, yoffset])
else:
if row % 2 == 0:
self.place_inst.place(offset=[xoffset + self.place_inst.width, yoffset + self.place_inst.height], mirror="XY")
else:
self.place_inst.place(offset=[xoffset + self.place_inst.width, yoffset], mirror="MY")
xoffset += self.place_inst.width
if row == row_offset:
yoffset += self.colend.height
pattern.append_row_to_block(bit_block, replica_row_opt1a)
else:
yoffset += self.place_inst.height
if current_row % 2 == 0:
pattern.append_row_to_block(bit_block, dummy_row_opt1)
else:
pattern.append_row_to_block(bit_block, dummy_row_opt1a)
current_row += 1
self.pattern = pattern(self, "replica_column", bit_block, num_rows=self.total_size, num_cols=len(replica_row_opt1), name_template="rbc_r{0}_c{1}")
self.pattern.connect_array_raw()
self.width = max([x.rx() for x in self.insts])
self.height = max([x.uy() for x in self.insts])
def add_layout_pins(self):
""" Add the layout pins """
for port in self.all_ports:
bl_pin = self.cell_inst[2].get_pin(self.cell.get_bl_name(port))
self.add_layout_pin(text="bl_{0}_{1}".format(port, 0),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
bl_pin = self.cell_inst[2].get_pin(self.cell.get_br_name(port))
self.add_layout_pin(text="br_{0}_{1}".format(port, 0),
layer=bl_pin.layer,
offset=bl_pin.ll().scale(1, 0),
width=bl_pin.width(),
height=self.height)
row_range_max = self.total_size - 1
row_range_min = 1
for port in self.all_ports:
for row in range(row_range_min, row_range_max):
wl_pin = self.cell_inst[row].get_pin(self.cell.get_wl_name(port))
self.add_layout_pin(text="wl_{0}_{1}".format(port, row_range_max-row),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
# for colend in [self.cell_inst[0], self.cell_inst[self.row_size]]:
# inst = self.cell_inst[row]
# for pin_name in ["top_gate", "bot_gate"]:
# pin = inst.get_pin("gate")
# self.add_layout_pin(text=pin_name,
# layer=pin.layer,
# offset=pin.ll(),
# width=pin.width(),
# height=pin.height())
for row in range(self.row_size + 2):
inst = self.cell_inst[row]
# add only 1 label per col
for pin_name in ["vdd", "gnd"]:
self.copy_layout_pin(inst, pin_name)
#if row == 2:
if 'VPB' or 'vpb' in self.cell_inst[row].mod.pins:
pin = inst.get_pin("vpb")
self.objs.append(geometry.rectangle(layer["nwell"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("vdd", layer["nwell"], pin.center()))
if 'VNB' or 'vnb' in self.cell_inst[row].mod.pins:
try:
from openram.tech import layer_override
if layer_override['VNB']:
pin = inst.get_pin("vnb")
self.add_label("gnd", pin.layer, pin.center())
self.objs.append(geometry.rectangle(layer["pwellp"],
pin.ll(),
pin.width(),
pin.height()))
self.objs.append(geometry.label("gnd", layer["pwellp"], pin.center()))
except:
pin = inst.get_pin("vnb")
self.add_label("gnd", pin.layer, pin.center())
def exclude_all_but_replica(self):
"""
Excludes all bits except the replica cell (self.replica_bit).
"""
for row, cell in self.cell_inst.items():
if row != self.replica_bit:
self.graph_inst_exclude.add(cell)

View File

@ -5,140 +5,98 @@
# All rights reserved.
#
from openram.base import geometry
from openram.sram_factory import factory
from openram import OPTS
from .sky130_bitcell_base_array import sky130_bitcell_base_array
from openram.modules.row_cap_array import row_cap_array
from openram.modules.pattern import pattern
from math import ceil
class sky130_row_cap_array(sky130_bitcell_base_array):
class sky130_row_cap_array(row_cap_array, sky130_bitcell_base_array):
"""
Generate a dummy row/column for the replica array.
"""
def __init__(self, rows, cols, column_offset=0, mirror=0, name=""):
# Don't call the regular col-cap_array constructor since we don't want its constructor, just
# some of it's useful member functions
sky130_bitcell_base_array.__init__(self, rows=rows, cols=cols, column_offset=column_offset, name=name)
self.rows = rows
self.cols = cols
self.column_offset = column_offset
def __init__(self, rows, cols, column_offset=0, row_offset=0, mirror=0, location="", name=""):
super().__init__(rows, cols, column_offset=column_offset, location=location, name=name)
self.mirror = mirror
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
""" Create and connect the netlist """
self.create_all_wordline_names()
# This module has no bitlines
# self.create_all_bitline_names()
self.add_modules()
self.add_pins()
self.create_instances()
def create_layout(self):
self.place_array("dummy_r{0}_c{1}", self.mirror)
self.add_layout_pins()
self.width = max([x.rx() for x in self.insts])
self.height = max([x.uy() for x in self.insts])
self.add_boundary()
self.DRC_LVS()
self.location = location
def add_modules(self):
""" Add the modules used in this design """
if self.column_offset == 0:
if self.location == "left":
self.top_corner = factory.create(module_type="corner", location="ul")
self.bottom_corner =factory.create(module_type="corner", location="ll")
self.rowend1 = factory.create(module_type="row_cap", version="rowend_replica")
self.rowend2 = factory.create(module_type="row_cap", version="rowenda_replica")
#self.rowend1 = factory.create(module_type="row_cap", version="rowend_replica")
#self.rowend2 = factory.create(module_type="row_cap", version="rowenda_replica")
else:
self.top_corner = factory.create(module_type="corner", location="ur")
self.bottom_corner = factory.create(module_type="corner", location="lr")
self.rowend1 = factory.create(module_type="row_cap", version="rowend")
self.rowend2 = factory.create(module_type="row_cap", version="rowenda")
#self.rowend1 = factory.create(module_type="row_cap", version="rowend")
#self.rowend2 = factory.create(module_type="row_cap", version="rowenda")
self.rowend = factory.create(module_type="row_cap", version="rowend")
self.rowenda = factory.create(module_type="row_cap", version="rowenda")
self.cell = factory.create(module_type=OPTS.bitcell, version="opt1")
def create_instances(self):
""" Create the module instances used in this design """
self.cell_inst = {}
self.array_layout = []
alternate_bitcell = (self.rows + 1) % 2
for row in range(self.rows + 2):
row_layout = []
name="rca_{0}".format(row)
# Top/bottom cell are always dummy cells.
# Regular array cells are replica cells (>left_rbl and <rows-right_rbl)
# Replic bit specifies which other bit (in the full range (0,rows) to make a replica cell.
self.all_inst={}
self.cell_inst={}
bit_block = []
if self.location == "left":
top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False, mirror="MY")
bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="XY")
rowend = geometry.instance("row_cap_rowend", mod=self.rowend, is_bitcell=True, mirror="XY")
rowenda = geometry.instance("row_cap_rowenda", mod=self.rowenda, is_bitcell=True, mirror="MY")
elif self.location == "right":
top_corner = geometry.instance("row_cap_top_corner", mod=self.top_corner, is_bitcell=False)
bottom_corner = geometry.instance("row_cap_bottom_corner", mod=self.bottom_corner, is_bitcell=False, mirror="MX")
rowend = geometry.instance("row_cap_rowend", mod=self.rowend, is_bitcell=True, mirror="MX")
rowenda = geometry.instance("row_cap_rowenda", mod=self.rowenda, is_bitcell=True)
if (row < self.rows + 1 and row > 0):
pattern.append_row_to_block(bit_block, [bottom_corner])
for row in range(1, self.row_size-1):
if row % 2 == 1:
pattern.append_row_to_block(bit_block, [rowenda])
if alternate_bitcell == 0:
row_layout.append(self.rowend1)
self.cell_inst[row]=self.add_inst(name=name, mod=self.rowend1)
self.connect_inst(["wl_0_{}".format(row - 1), "vdd"])
alternate_bitcell = 1
else:
row_layout.append(self.rowend2)
self.cell_inst[row] = self.add_inst(name=name, mod=self.rowend2)
self.connect_inst(["wl_0_{}".format(row - 1), "vdd"])
alternate_bitcell = 0
elif (row == 0):
row_layout.append(self.bottom_corner)
self.cell_inst[row]=self.add_inst(name=name, mod=self.bottom_corner)
self.connect_inst(self.get_corner_pins())
elif (row == self.rows + 1):
row_layout.append(self.top_corner)
self.cell_inst[row]=self.add_inst(name=name, mod=self.top_corner)
self.connect_inst(self.get_corner_pins())
self.array_layout.append(row_layout)
def place_array(self, name_template, row_offset=0):
xoffset = 0.0
yoffset = 0.0
for row in range(len(self.insts)):
inst = self.insts[row]
if row == 0:
inst.place(offset=[xoffset, yoffset + inst.height], mirror="MX")
elif row == len(self.insts)-1:
inst.place(offset=[xoffset, yoffset])
else:
if row % 2 ==0:
inst.place(offset=[xoffset, yoffset + inst.height], mirror="MX")
else:
inst.place(offset=[xoffset, yoffset])
yoffset += inst.height
pattern.append_row_to_block(bit_block, [rowend])
def add_pins(self):
for row in range(self.rows + 2):
for port in self.all_ports:
self.add_pin("wl_{}_{}".format(port, row), "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
pattern.append_row_to_block(bit_block, [top_corner])
self.pattern = pattern(self, "row_cap_array_" + self.location, bit_block, num_rows=self.row_size, num_cols=self.column_size, num_cores_x=ceil(self.column_size/2), num_cores_y=ceil(self.row_size/2), name_template="row_cap_array" + self.location + "_r{0}_c{1}")
self.pattern.connect_array_raw()
def get_bitcell_pins(self, row, col):
"""
Creates a list of connections in the bitcell,
indexed by column and row, for instance use in bitcell_array
"""
def add_layout_pins(self):
""" Add the layout pins """
for row in range(0, self.rows + 1):
if row > 0 and row < self.rows + 1:
wl_pin = self.cell_inst[row].get_pin("wl")
self.add_layout_pin(text="wl_0_{0}".format(row -1),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_pin.height())
bitcell_pins = []
bitcell_pins.append("vdd") # vdd
bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
# Add vdd/gnd via stacks
for row in range(1, self.rows):
inst = self.cell_inst[row]
for pin_name in ["vdd", "gnd"]:
for pin in inst.get_pins(pin_name):
self.copy_layout_pin(inst, pin_name)
return bitcell_pins
def get_strap_pins(self, row, col):
strap_pins = []
strap_pins.append("vdd") # vdd
strap_pins.append("vdd") # vpb
strap_pins.append("gnd") # vnb
return strap_pins
def create_layout(self):
self.place_array()
self.add_layout_pins()
self.add_boundary()
self.DRC_LVS()

View File

@ -5,837 +5,15 @@
# All rights reserved.
#
import sys
from openram import OPTS
import os
from openram import drc as d
import os
dir_path = os.path.dirname(os.path.realpath(__file__))
"""
File containing the process technology parameters for Skywater 130nm.
"""
sys.path.append("{}/{}".format(dir_path,'tech_configs'))
###################################################
# Custom modules
###################################################
# This uses the default classes to instantiate module from
# '$OPENRAM_HOME/compiler/modules'.
# Using tech_modules['cellname'] you can override each class by providing a custom
# implementation in '$OPENRAM_TECHDIR/modules/'
# For example: tech_modules["contact"] = "contact_freepdk45"
tech_modules = d.module_type()
# These modules have been hand designed and provided in this repository.
tech_modules["nand2_dec"] = "nand2_dec"
tech_modules["nand3_dec"] = "nand3_dec"
tech_modules["nand4_dec"] = "nand4_dec"
# Override default OpenRAM modules to sky130 modules
# These are for single port and dual port as a list,
# or for both if there is no list,
# or only applicable to one if there is no list.
tech_modules["bitcell_1port"] = "sky130_bitcell"
tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell"
tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell"
tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port"
tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port"
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"]
tech_modules["col_cap_array"] = ["sky130_col_cap_array", "col_cap_array"]
tech_modules["col_cap"] = ["sky130_col_cap", "col_cap_bitcell_2port"]
tech_modules["corner"] = ["sky130_corner", None]
tech_modules["internal"] = ["sky130_internal", None]
tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"]
tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"]
# These modules are auto-generated from the nand decoders above and are not
# found in this.
tech_modules["buf_dec"] = "pbuf_dec"
tech_modules["inv_dec"] = "pinv_dec"
tech_modules["and2_dec"] = "and2_dec"
tech_modules["and3_dec"] = "and3_dec"
tech_modules["and4_dec"] = "and4_dec"
###################################################
# Custom cell properties
###################################################
cell_properties = d.cell_properties()
cell_properties.bitcell_power_pin_directions = ("H", "H")
cell_properties.bitcell_1port.mirror.x = True
cell_properties.bitcell_1port.mirror.y = True
cell_properties.bitcell_1port.end_caps = True
cell_properties.bitcell_1port.boundary_layer = "mem"
cell_properties.bitcell_1port.port_order = ['bl', 'br', 'gnd', 'vdd', 'vpb', 'vnb', 'wl']
cell_properties.bitcell_1port.port_types = ["OUTPUT", "OUTPUT", "GROUND", "POWER", "BIAS", "BIAS", "INPUT"]
cell_properties.bitcell_1port.port_map = {'bl': 'BL',
'br': 'BR',
'wl': 'WL',
'vdd': 'VPWR',
'vnb': 'VNB',
'vpb': 'VPB',
'gnd': 'VGND'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.bl_layer = "m1"
cell_properties.bitcell_1port.vdd_layer = "m1"
cell_properties.bitcell_1port.vdd_dir = "V"
cell_properties.bitcell_1port.gnd_layer = "m2"
cell_properties.bitcell_1port.gnd_dir = "H"
cell_properties.bitcell_2port.mirror.x = True
cell_properties.bitcell_2port.mirror.y = True
cell_properties.bitcell_2port.end_caps = True
cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd']
cell_properties.bitcell_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'wl0': 'WL0',
'wl1': 'WL1',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.vdd_layer = "m2"
cell_properties.bitcell_1port.vdd_dir = "H"
cell_properties.bitcell_1port.gnd_layer = "m2"
cell_properties.bitcell_1port.gnd_dir = "H"
cell_properties.bitcell_2port.wl_layer = "m2"
cell_properties.bitcell_2port.vdd_layer = "m1"
cell_properties.bitcell_2port.vdd_dir = "H"
cell_properties.bitcell_2port.gnd_layer = "m2"
cell_properties.bitcell_2port.gnd_dir = "H"
cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd', 'vpb', 'vnb', 'gate'],
['INPUT', 'INPUT','POWER', 'GROUND', 'BIAS', 'BIAS', 'INPUT'],
{'bl': 'bl',
'br': 'br',
'vdd': 'vdd',
'gnd': 'gnd',
'vnb': 'vnb',
'vpb': 'vpb',
'gate': 'gate'})
cell_properties.col_cap_1port_bitcell.boundary_layer = "mem"
cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'],
['POWER', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'vdd': 'VPWR'})
cell_properties.col_cap_1port_strap_power.boundary_layer = "mem"
cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'],
['GROUND', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'gnd': 'VGND'})
cell_properties.col_cap_1port_strap_ground.boundary_layer = "mem"
cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'],
['POWER', 'INPUT'],
{'wl': 'WL',
'vdd': 'VPWR'})
cell_properties.row_cap_1port_cell.boundary_layer = "mem"
cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd']
cell_properties.col_cap_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'vdd': 'VDD'}
cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd']
cell_properties.row_cap_2port.port_map = {'wl0': 'WL0',
'wl1': 'WL1',
'gnd': 'GND'}
cell_properties.ptx.bin_spice_models = True
cell_properties.ptx.model_is_subckt = True
cell_properties.pgate.add_implants = True
cell_properties.use_strap = True
cell_properties.strap_module = "internal"
cell_properties.strap_version = "wlstrap"
cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd']
cell_properties.dff.port_map = {'D': 'D',
'Q': 'Q',
'clk': 'CLK',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd']
cell_properties.nand2_dec.port_map = {'A': 'A',
'B': 'B',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd']
cell_properties.nand3_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd']
cell_properties.nand4_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'D': 'D',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd']
cell_properties.sense_amp.port_map = {'bl': 'BL',
'br': 'BR',
'dout': 'DOUT',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd']
cell_properties.write_driver.port_map = {'din': 'DIN',
'bl': 'BL',
'br': 'BR',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
# You can override the GDS for custom cell using the following:
# If it is a list, the first is single port and the second is dual port.
# If it is string, it is used for both single and dual port.
cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff"
cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_sp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"]
cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_sp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"]
cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_sp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"]
cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell"
cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy"
cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica"
cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col"
cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row"
cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp"
cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver"
array_row_multiple = 2
array_col_multiple = 2
###################################################
# Custom layer properties
###################################################
layer_properties = d.layer_properties()
layer_properties.hierarchical_decoder.bus_layer = "m1"
layer_properties.hierarchical_decoder.bus_directions = "nonpref"
layer_properties.hierarchical_decoder.input_layer = "li"
layer_properties.hierarchical_decoder.output_layer = "m2"
layer_properties.hierarchical_decoder.vertical_supply = True
layer_properties.hierarchical_predecode.bus_layer = "m1"
layer_properties.hierarchical_predecode.bus_directions = "nonpref"
# This is added to allow the column decoder connections on m2
layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2
layer_properties.hierarchical_predecode.bus_space_factor = 1.5
layer_properties.hierarchical_predecode.input_layer = "li"
layer_properties.hierarchical_predecode.output_layer = "m2"
layer_properties.hierarchical_predecode.vertical_supply = True
layer_properties.hierarchical_predecode.force_horizontal_input_contact = True
layer_properties.bank.stack = "m2_stack"
layer_properties.bank.pitch = "m3_pitch"
layer_properties.column_mux_array.select_layer = "m3"
layer_properties.column_mux_array.bitline_layer = "m1"
layer_properties.port_address.supply_offset = True
layer_properties.port_data.enable_layer = "m1"
layer_properties.port_data.channel_route_bitlines = False
layer_properties.replica_column.even_rows = True
layer_properties.wordline_driver.vertical_supply = True
layer_properties.global_wordline_layer = "m5"
###################################################
# Discrete tx bins
###################################################
# enforce that tx sizes are within 25% of requested size after fingering.
accuracy_requirement = 0.75
nmos_bins = {
0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
0.25 : [0.65, 1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0]
}
pmos_bins = {
0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12],
0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.25 : [1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42]
}
###################################################
# GDS file info
###################################################
GDS = {}
# gds units
# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first
# is the size of a database unit in user units. The second is the size
# of a database unit in meters. For example, if your library was
# created with the default units (user unit = 1 um and 1000 database
# units per user unit), then the first number would be 0.001 and the
# second number would be 10-9. Typically, the first number is less than
# 1, since you use more than 1 database unit per user unit. To
# calculate the size of a user unit in meters, divide the second number
# by the first."
GDS["unit"] = (0.001, 1e-9)
#GDS["unit"]=(0.001, 1e-6)
###################################################
# Interconnect stacks
###################################################
poly_stack = ("poly", "contact", "li")
active_stack = ("active", "contact", "li")
li_stack = ("li", "mcon", "m1")
m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
m4_stack = ("m4", "via4", "m5")
lef_rom_interconnect = ["m1", "m2", "m3", "m4"]
layer_indices = {"poly": 0,
"active": 0,
"nwell": 0,
"li": 1,
"m1": 2,
"m2": 3,
"m3": 4,
"m4": 5,
"m5": 6}
# The FEOL stacks get us up to m1
feol_stacks = [poly_stack,
active_stack,
li_stack]
# The BEOL stacks are m1 and up
beol_stacks = [m1_stack,
m2_stack,
m3_stack,
m4_stack]
layer_stacks = feol_stacks + beol_stacks
preferred_directions = {"poly": "V",
"active": "V",
"li": "V",
"m1": "H",
"m2": "V",
"m3": "H",
"m4": "V",
"m5": "H"}
###################################################
# GDS Layer Map
###################################################
layer = {}
layer["active"] = (65, 20) # diff
layer["activep"] = (65, 20) # diff
layer["tap"] = (65, 44) # tap
layer["pwellp"] = (122,16)
layer["nwell"] = (64, 20) # nwell
layer["dnwell"] = (64,18)
layer["nimplant"]= (93, 44) # nsdm
layer["pimplant"]= (94, 20) # psdm
layer["vtl"] = (125, 44) # lvtn
layer["vth"] = (78, 44) # hvtp (pmos only)
layer["thkox"] = (8, 0)
layer["poly"] = (66, 20)
layer["contact"] = (66, 44) # licon1
layer["npc"] = (95, 20) # npc (nitride cut)
layer["li"] = (67, 20) # active li1
layer["mcon"] = (67, 44) # mcon
layer["m1"] = (68, 20) # met1
layer["m1p"] = (68, 5) # met1 pin
layer["via1"] = (68, 44) # via1
layer["m2"] = (69, 20) # met2
layer["m2p"] = (69, 5) # met2 pin
layer["via2"] = (69, 44) # via2
layer["m3"] = (70, 20) # met3
layer["m3p"] = (70, 5) # met3 pin
layer["via3"] = (70, 44) # via3
layer["m4"] = (71, 20) # met4
layer["m4p"] = (71, 5) # met4 pin
layer["via4"] = (71, 44) # via4
layer["m5"] = (72, 20) # met5
layer["m5p"] = (72, 5) # met5 pin
layer["boundary"]= (235, 4)
# specific boundary type to define standard cell regions for DRC
layer["stdc"] = (81, 4)
layer["mem"] = (81, 2)
# Not an official sky130 layer, but useful for router debug infos
layer["text"]= (234, 5)
# Excpected value according to sky130A tech file
# If calibre is enabled, these will be swapped below
#pin_purpose = 5
label_purpose = 5
#label_purpose = 16
#pin_purpose = 16
#label_purpose = 5
# pin_read purposes
special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]}
#layer_override = {"VNB\x00": ["pwell",122]}
layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]}
layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"}
layer_override_purpose = {122: (64, 59)}
# Layer names for external PDKs
layer_names = {}
layer_names["active"] = "diff"
layer_names["activep"] = "diff"
layer_names["tap"] = "tap"
layer_names["pwellp"] = "pwellp"
layer_names["nwell"] = "nwell"
layer_names["dnwell"] = "dnwell"
layer_names["nimplant"]= "nsdm"
layer_names["pimplant"]= "psdm"
layer_names["vtl"] = "lvtn"
layer_names["vth"] = "hvtp"
layer_names["thkox"] = "thkox"
layer_names["poly"] = "poly"
layer_names["contact"] = "licon1"
layer_names["li"] = "li1"
layer_names["mcon"] = "mcon"
layer_names["m1"] = "met1"
layer_names["m1p"] = "met1"
layer_names["via1"] = "via"
layer_names["m2"] = "met2"
layer_names["m2p"] = "met2"
layer_names["via2"] = "via2"
layer_names["m3"] = "met3"
layer_names["m3p"] = "met3"
layer_names["via3"] = "via3"
layer_names["m4"] = "met4"
layer_names["m4p"] = "met4"
layer_names["via4"] = "via4"
layer_names["m5p"] = "met5"
layer_names["boundary"]= "boundary"
layer_names["stdc"] = "areaid.standardc"
layer_names["mem"] = "areaid.core"
layer_names["text"] = "text"
###################################################
# DRC/LVS Rules Setup
###################################################
# technology parameter
parameter={}
# difftap.2b
parameter["min_tx_size"] = 0.150
parameter["beta"] = 3
parameter["6T_inv_nmos_size"] = 0.205
parameter["6T_inv_pmos_size"] = 0.09
parameter["6T_access_size"] = 0.135
drc = d.design_rules("sky130")
# grid size
drc["grid"] = 0.005
#DRC/LVS test set_up
# Switching between calibre and magic can be useful for development,
# it eventually should be deleted.
NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False)
use_calibre = bool(NDA_PDK_ROOT)
use_calibre = False
use_klayout = False
if use_calibre:
# Correct order according to s8
pin_purpose = 16
label_purpose = 5
drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules"
drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts"
drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm"
drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap"
# minwidth_tx with contact (no dog bone transistors)
# difftap.2b
drc["minwidth_tx"] = 0.360
drc["minlength_channel"] = 0.150
drc["pwell_to_nwell"] = 0
# nwell.1 Minimum width of nwell/pwell
drc.add_layer("nwell",
width=0.840,
spacing=1.270)
# poly.1a Minimum width of poly
# poly.2 Minimum spacing of poly AND active
drc.add_layer("poly",
width=0.150,
spacing=0.210)
# poly.8
drc["poly_extend_active"] = 0.13
# Not a rule
drc["poly_to_contact"] = 0
# poly.7 Minimum enclosure of active around gate
drc["active_enclose_gate"] = 0.075
# poly.4 Minimum spacing of field poly to active
drc["poly_to_active"] = 0.075
# poly.2 Minimum spacing of field poly
drc["poly_to_field_poly"] = 0.210
# difftap.1 Minimum width of active
# difftap.3 Minimum spacing of active
drc.add_layer("active",
width=0.150,
spacing=0.270)
# difftap.8
drc.add_enclosure("nwell",
layer="active",
enclosure=0.18,
extension=0.18)
# nsd/psd.5a
drc.add_enclosure("implant",
layer="active",
enclosure=0.125)
# Same as active enclosure?
drc["implant_to_contact"] = 0.070
# nsd/psd.1 nsd/psd.2
drc.add_layer("implant",
width=0.380,
spacing=0.380,
area=0.265)
# licon.1, licon.2
drc.add_layer("contact",
width=0.170,
spacing=0.170)
# licon.5c (0.06 extension), (licon.7 for extension)
drc.add_enclosure("active",
layer="contact",
enclosure=0.040,
extension=0.060)
# licon.7
drc["tap_extend_contact"] = 0.120
# licon.8 Minimum enclosure of poly around contact
drc.add_enclosure("poly",
layer="contact",
enclosure=0.08,
extension=0.08)
# licon.11a
drc["active_contact_to_gate"] = 0.050
# npc.4 > licon.14 0.19 > licon.11a
drc["poly_contact_to_gate"] = 0.270
# licon.15
drc["npc_enclose_poly"] = 0.1
# li.1, li.3
drc.add_layer("li",
width=0.170,
spacing=0.170)
# licon.5
drc.add_enclosure("li",
layer="contact",
enclosure=0,
extension=0.080)
drc.add_enclosure("li",
layer="mcon",
enclosure=0,
extension=0.080)
# mcon.1, mcon.2
drc.add_layer("mcon",
width=0.170,
spacing=0.210)
# m1.1 Minimum width of metal1
# m1.2 Minimum spacing of metal1
# m1.6 Minimum area of metal1
drc.add_layer("m1",
width=0.140,
spacing=0.140,
area=0.083)
# m1.4 Minimum enclosure of metal1
# m1.5 Minimum enclosure around contact on two opposite sides
drc.add_enclosure("m1",
layer="mcon",
enclosure=0.030,
extension=0.060)
# via.4a Minimum enclosure around via1
# via.5a Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m1",
layer="via1",
enclosure=0.055,
extension=0.085)
# via.1a Minimum width of via1
# via.2 Minimum spacing of via1
drc.add_layer("via1",
width=0.150,
spacing=0.170)
# m2.1 Minimum width of intermediate metal
# m2.2 Minimum spacing of intermediate metal
# m2.6 Minimum area of metal2
drc.add_layer("m2",
width=0.140,
spacing=0.140,
area=0.0676)
# m2.4 Minimum enclosure around via1
# m2.5 Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m2",
layer="via1",
enclosure=0.055,
extension=0.085)
# via2.4 Minimum enclosure around via2
# via2.5 Minimum enclosure around via2 on two opposite sides
drc.add_enclosure("m2",
layer="via2",
enclosure=0.040,
extension=0.085)
# via2.1a Minimum width of Via2
# via2.2 Minimum spacing of Via2
drc.add_layer("via2",
width=0.200,
spacing=0.200)
# m3.1 Minimum width of metal3
# m3.2 Minimum spacing of metal3
# m3.6 Minimum area of metal3
drc.add_layer("m3",
width=0.300,
spacing=0.300,
area=0.240)
# m3.4 Minimum enclosure around via2
drc.add_enclosure("m3",
layer="via2",
enclosure=0.065)
# via3.4 Minimum enclosure around via3
# via3.5 Minimum enclosure around via3 on two opposite sides
drc.add_enclosure("m3",
layer="via3",
enclosure=0.060,
extension=0.090)
# via3.1 Minimum width of Via3
# via3.2 Minimum spacing of Via3
drc.add_layer("via3",
width=0.200,
spacing=0.200)
# m4.1 Minimum width of metal4
# m4.2 Minimum spacing of metal4
# m4.7 Minimum area of metal4
drc.add_layer("m4",
width=0.300,
spacing=0.300,
area=0.240)
# m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via3",
enclosure=0.065)
# FIXME: Wrong rule m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via4",
enclosure=0.060)
# via4.1 Minimum width of Via4
# via4.2 Minimum spacing of Via4
drc.add_layer("via4",
width=0.800,
spacing=0.800)
# FIXME: Wrong rules
# m5.1 Minimum width of metal5
# m5.2 Minimum spacing of metal5
# m5.7 Minimum area of metal5
drc.add_layer("m5",
width=1.600,
spacing=1.600,
area=4.000)
# m5.3 Minimum enclosure around via4
drc.add_enclosure("m5",
layer="via4",
enclosure=0.310)
# Metal 5-10 are ommitted
###################################################
# Spice Simulation Parameters
###################################################
# spice info
spice = {}
spice["nmos"] = "sky130_fd_pr__nfet_01v8"
spice["pmos"] = "sky130_fd_pr__pfet_01v8"
spice["power"]="vccd1"
spice["ground"]="vssd1"
# whether or not the device model is actually a subckt
spice["device_prefix"] = "X"
spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]],
"SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]],
"FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]],
"SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]],
"FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] }
# spice stimulus related variables
spice["feasible_period"] = 10 # estimated feasible period in ns
spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts]
spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts]
spice["rise_time"] = 0.005 # rise time in [Nano-seconds]
spice["fall_time"] = 0.005 # fall time in [Nano-seconds]
spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius)
spice["nom_temperature"] = 25 # Nominal temperature (celcius)
# analytical delay parameters
spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts
spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square
spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2
spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff
spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff
spice["dff_setup"] = 102.5391 # DFF setup time in ps
spice["dff_hold"] = -56 # DFF hold time in ps
spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad]
spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad]
# analytical power parameters, many values are temporary
spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW
spice["inv_leakage"] = 1 # Leakage power of inverter in nW
spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW
spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW
spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW
spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW
spice["dff_leakage"] = 1 # Leakage power of flop in nW
spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz
# Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["le_tau"] = 2.25 # In pico-seconds.
parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad
parameter["dff_clk_cin"] = 30.6 # relative capacitance
parameter["6tcell_wl_cin"] = 3 # relative capacitance
parameter["min_inv_para_delay"] = 2.4 # Tau delay units
parameter["sa_en_pmos_size"] = 0.72 # micro-meters
parameter["sa_en_nmos_size"] = 0.27 # micro-meters
parameter["sa_inv_pmos_size"] = 0.54 # micro-meters
parameter["sa_inv_nmos_size"] = 0.27 # micro-meters
parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance
###################################################
# Technology Tool Preferences
###################################################
if use_calibre:
drc_name = "calibre"
lvs_name = "calibre"
pex_name = "calibre"
elif use_klayout:
drc_name = "klayout"
lvs_name = "klayout"
pex_name = "klayout"
else:
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"
# This is used by uniqify to not rename the library cells
library_prefix_name = "sky130_fd_bd_sram__"
# List of cells to skip running DRC/LVS on directly
# This will look for a maglef file and copy it over the mag file
# before DRC after extraction
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs
flatglob = ["*_?mos_m*",
"sky130_fd_bd_sram__sram_sp_cell_fom_serifs",
"sky130_fd_bd_sram__sram_sp_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"]
blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell",
"sky130_fd_bd_sram__openram_dp_cell_dummy",
"sky130_fd_bd_sram__openram_dp_cell_replica",
"sky130_fd_bd_sram__sram_sp_cell_opt1a",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__sram_sp_cell_opt1",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica",
"sky130_fd_bd_sram__sram_sp_colend",
"sky130_fd_bd_sram__sram_sp_colend_cent",
"sky130_fd_bd_sram__sram_sp_colend_p_cent",
"sky130_fd_bd_sram__sram_sp_colenda",
"sky130_fd_bd_sram__sram_sp_colenda_cent",
"sky130_fd_bd_sram__sram_sp_colenda_p_cent",
"sky130_fd_bd_sram__sram_sp_rowend",
"sky130_fd_bd_sram__sram_sp_rowenda",
"sky130_fd_bd_sram__openram_sp_rowend_replica",
"sky130_fd_bd_sram__openram_sp_rowenda_replica",
"sky130_fd_bd_sram__sram_sp_corner",
"sky130_fd_bd_sram__sram_sp_cornera",
"sky130_fd_bd_sram__sram_sp_cornerb",
"sky130_fd_bd_sram__sram_sp_wlstrapa",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p"]
if not hasattr(OPTS, 'tech_file'):
OPTS.tech_file = 'tech_cypress_cell'
#TODO: FIX THIS TERRIBLE HACK JUST FOR TESTING
exec('from {} import *'.format(OPTS.tech_file))

View File

@ -0,0 +1,857 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California
# All rights reserved.
#
import os
from openram import drc as d
"""
File containing the process technology parameters for Skywater 130nm.
"""
###################################################
# Custom modules
###################################################
# This uses the default classes to instantiate module from
# '$OPENRAM_HOME/compiler/modules'.
# Using tech_modules['cellname'] you can override each class by providing a custom
# implementation in '$OPENRAM_TECHDIR/modules/'
# For example: tech_modules["contact"] = "contact_freepdk45"
tech_modules = d.module_type()
# These modules have been hand designed and provided in this repository.
tech_modules["nand2_dec"] = "nand2_dec"
tech_modules["nand3_dec"] = "nand3_dec"
tech_modules["nand4_dec"] = "nand4_dec"
# Override default OpenRAM modules to sky130 modules
# These are for single port and dual port as a list,
# or for both if there is no list,
# or only applicable to one if there is no list.
#tech_modules["bitcell_1port"] = "sky130_bitcell"
#tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell"
#tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell"
tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port"
tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port"
tech_modules["bitcell_2port"] = "bitcell_2port"
tech_modules["bitcell_array"] = ["bitcell_array", "bitcell_array"]
tech_modules["replica_bitcell_array"] = ["replica_bitcell_array", "replica_bitcell_array"]
tech_modules["capped_replica_bitcell_array"] = ["capped_replica_bitcell_array", "capped_replica_bitcell_array"]
tech_modules["dummy_array"] = ["dummy_array", "dummy_array"]
tech_modules["replica_column"] = ["replica_column", "replica_column"]
tech_modules["col_cap_array"] = ["col_cap_array", "col_cap_array"]
tech_modules["col_cap"] = ["col_cap_bitcell_1port", "col_cap_bitcell_2port"]
tech_modules["corner"] = ["sky130_corner", None]
tech_modules["internal"] = ["sky130_internal", None]
tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"]
tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"]
# These modules are auto-generated from the nand decoders above and are not
# found in this.
tech_modules["buf_dec"] = "pbuf_dec"
tech_modules["inv_dec"] = "pinv_dec"
tech_modules["and2_dec"] = "and2_dec"
tech_modules["and3_dec"] = "and3_dec"
tech_modules["and4_dec"] = "and4_dec"
###################################################
# Custom cell properties
###################################################
cell_properties = d.cell_properties()
cell_properties.power_name = 'VPWR'
cell_properties.ground_name = 'VGND'
cell_properties.bitcell_power_pin_directions = ("V", "V")
cell_properties.bitcell_1port.mirror.x = True
cell_properties.bitcell_1port.mirror.y = True
cell_properties.bitcell_1port.end_caps = False
cell_properties.bitcell_1port.has_corners = True
cell_properties.bitcell_1port.boundary_layer = "boundary"
cell_properties.bitcell_1port.port_order = ['bl', 'br', 'wl', 'vdd', 'gnd']
cell_properties.bitcell_1port.port_types = ["INPUT", "INPUT", "GROUND", "POWER", "OUTPUT"]
cell_properties.bitcell_1port.port_map = {'bl': 'BL',
'br': 'BR',
'gnd': 'VGND',
'vdd': 'VPWR',
'wl': 'WL'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.bl_layer = "m1"
cell_properties.bitcell_1port.vdd_layer = "m1"
cell_properties.bitcell_1port.vdd_dir = "V"
cell_properties.bitcell_1port.gnd_layer = "m1"
cell_properties.bitcell_1port.gnd_dir = "V"
cell_properties.bitcell_2port.mirror.x = True
cell_properties.bitcell_2port.mirror.y = True
cell_properties.bitcell_2port.end_caps = True
cell_properties.bitcell_2port.has_corners = True
cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd']
cell_properties.bitcell_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'wl0': 'WL0',
'wl1': 'WL1',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.vdd_layer = "m1"
cell_properties.bitcell_1port.vdd_dir = "V"
cell_properties.bitcell_1port.gnd_layer = "m1"
cell_properties.bitcell_1port.gnd_dir = "V"
cell_properties.bitcell_2port.wl_layer = "m2"
cell_properties.bitcell_2port.vdd_layer = "m1"
cell_properties.bitcell_2port.vdd_dir = "H"
cell_properties.bitcell_2port.gnd_layer = "m2"
cell_properties.bitcell_2port.gnd_dir = "H"
cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd',],
['INPUT', 'INPUT','POWER', 'GROUND', ],
{'bl': 'bl',
'br': 'br',
'vdd': 'vdd',
'gnd': 'gnd',})
cell_properties.col_cap_1port_bitcell.boundary_layer = "boundary"
cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'],
['POWER', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'vdd': 'VPWR'})
cell_properties.col_cap_1port_strap_power.boundary_layer = "boundary"
cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'],
['GROUND', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'gnd': 'VGND'})
cell_properties.col_cap_1port_strap_ground.boundary_layer = "boundary"
cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'],
['POWER', 'INPUT'],
{'wl': 'WL',
'vdd': 'VPWR'})
cell_properties.row_cap_1port_cell.boundary_layer = "boundary"
cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd']
cell_properties.col_cap_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'vdd': 'VDD'}
cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd']
cell_properties.row_cap_2port.port_map = {'wl0': 'WL0',
'wl1': 'WL1',
'gnd': 'GND'}
cell_properties.ptx.bin_spice_models = True
cell_properties.ptx.model_is_subckt = True
cell_properties.pgate.add_implants = True
cell_properties.use_strap = False
cell_properties.strap_module = "internal"
cell_properties.strap_version = "wlstrap"
cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd']
cell_properties.dff.port_map = {'D': 'D',
'Q': 'Q',
'clk': 'CLK',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd']
cell_properties.nand2_dec.port_map = {'A': 'A',
'B': 'B',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd']
cell_properties.nand3_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd']
cell_properties.nand4_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'D': 'D',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd']
cell_properties.sense_amp.port_map = {'bl': 'BL',
'br': 'BR',
'dout': 'DOUT',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd']
cell_properties.write_driver.port_map = {'din': 'DIN',
'bl': 'BL',
'br': 'BR',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
# You can override the GDS for custom cell using the following:
# If it is a list, the first is single port and the second is dual port.
# If it is string, it is used for both single and dual port.
cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff"
cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_dp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"]
cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_dp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"]
cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_dp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"]
cell_properties.names["bitcell_1port"] = "sky130_custom_cell"
cell_properties.names["replica_bitcell_1port"] = "sky130_custom_replica"
cell_properties.names["dummy_bitcell_1port"] = "sky130_custom_dummy"
cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell"
cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy"
cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica"
cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col"
cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row"
cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp"
cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver"
array_row_multiple = 2
array_col_multiple = 2
###################################################
# Custom layer properties
###################################################
layer_properties = d.layer_properties()
layer_properties.hierarchical_decoder.bus_layer = "m1"
layer_properties.hierarchical_decoder.bus_directions = "nonpref"
layer_properties.hierarchical_decoder.input_layer = "li"
layer_properties.hierarchical_decoder.output_layer = "m2"
layer_properties.hierarchical_decoder.vertical_supply = True
layer_properties.hierarchical_predecode.bus_layer = "m1"
layer_properties.hierarchical_predecode.bus_directions = "nonpref"
# This is added to allow the column decoder connections on m2
layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2
layer_properties.hierarchical_predecode.bus_space_factor = 1.5
layer_properties.hierarchical_predecode.input_layer = "li"
layer_properties.hierarchical_predecode.output_layer = "m2"
layer_properties.hierarchical_predecode.vertical_supply = True
layer_properties.hierarchical_predecode.force_horizontal_input_contact = True
layer_properties.bank.stack = "m2_stack"
layer_properties.bank.pitch = "m3_pitch"
layer_properties.column_mux_array.select_layer = "m3"
layer_properties.column_mux_array.bitline_layer = "m1"
layer_properties.port_address.supply_offset = True
layer_properties.port_data.enable_layer = "m1"
layer_properties.port_data.channel_route_bitlines = False
layer_properties.replica_column.even_rows = False
layer_properties.wordline_driver.vertical_supply = True
layer_properties.global_wordline_layer = "m5"
###################################################
# Discrete tx bins
###################################################
# enforce that tx sizes are within 25% of requested size after fingering.
accuracy_requirement = 0.75
nmos_bins = {
0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
0.25 : [0.65, 1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0]
}
pmos_bins = {
0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12],
0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.25 : [1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42]
}
###################################################
# GDS file info
###################################################
GDS = {}
# gds units
# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first
# is the size of a database unit in user units. The second is the size
# of a database unit in meters. For example, if your library was
# created with the default units (user unit = 1 um and 1000 database
# units per user unit), then the first number would be 0.001 and the
# second number would be 10-9. Typically, the first number is less than
# 1, since you use more than 1 database unit per user unit. To
# calculate the size of a user unit in meters, divide the second number
# by the first."
GDS["unit"] = (0.001, 1e-9)
#GDS["unit"]=(0.001, 1e-6)
###################################################
# Interconnect stacks
###################################################
poly_stack = ("poly", "contact", "li")
active_stack = ("active", "contact", "li")
li_stack = ("li", "mcon", "m1")
m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
m4_stack = ("m4", "via4", "m5")
lef_rom_interconnect = ["m1", "m2", "m3", "m4"]
layer_indices = {"poly": 0,
"active": 0,
"nwell": 0,
"li": 1,
"m1": 2,
"m2": 3,
"m3": 4,
"m4": 5,
"m5": 6}
# The FEOL stacks get us up to m1
feol_stacks = [poly_stack,
active_stack,
li_stack]
# The BEOL stacks are m1 and up
beol_stacks = [m1_stack,
m2_stack,
m3_stack,
m4_stack]
layer_stacks = feol_stacks + beol_stacks
preferred_directions = {"poly": "V",
"active": "V",
"li": "V",
"m1": "H",
"m2": "V",
"m3": "H",
"m4": "V",
"m5": "H"}
###################################################
# GDS Layer Map
###################################################
layer = {}
layer["active"] = (65, 20) # diff
layer["activep"] = (65, 20) # diff
layer["tap"] = (65, 44) # tap
layer["pwellp"] = (122,16)
layer["nwell"] = (64, 20) # nwell
layer["dnwell"] = (64,18)
layer["nimplant"]= (93, 44) # nsdm
layer["pimplant"]= (94, 20) # psdm
layer["vtl"] = (125, 44) # lvtn
layer["vth"] = (78, 44) # hvtp (pmos only)
layer["thkox"] = (8, 0)
layer["poly"] = (66, 20)
layer["contact"] = (66, 44) # licon1
layer["npc"] = (95, 20) # npc (nitride cut)
layer["li"] = (67, 20) # active li1
layer["mcon"] = (67, 44) # mcon
layer["m1"] = (68, 20) # met1
layer["m1p"] = (68, 5) # met1 pin
layer["via1"] = (68, 44) # via1
layer["m2"] = (69, 20) # met2
layer["m2p"] = (69, 5) # met2 pin
layer["via2"] = (69, 44) # via2
layer["m3"] = (70, 20) # met3
layer["m3p"] = (70, 5) # met3 pin
layer["via3"] = (70, 44) # via3
layer["m4"] = (71, 20) # met4
layer["m4p"] = (71, 5) # met4 pin
layer["via4"] = (71, 44) # via4
layer["m5"] = (72, 20) # met5
layer["m5p"] = (72, 5) # met5 pin
layer["boundary"]= (235, 4)
# specific boundary type to define standard cell regions for DRC
layer["stdc"] = (81, 4)
layer["mem"] = (81, 2)
# Not an official sky130 layer, but useful for router debug infos
layer["text"]= (234, 5)
# Excpected value according to sky130A tech file
# If calibre is enabled, these will be swapped below
#pin_purpose = 5
label_purpose = 5
#label_purpose = 16
#pin_purpose = 16
#label_purpose = 5
# pin_read purposes
special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]}
#layer_override = {"VNB\x00": ["pwell",122]}
layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]}
layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"}
layer_override_purpose = {122: (64, 59)}
# Layer names for external PDKs
layer_names = {}
layer_names["active"] = "diff"
layer_names["activep"] = "diff"
layer_names["tap"] = "tap"
layer_names["pwellp"] = "pwellp"
layer_names["nwell"] = "nwell"
layer_names["dnwell"] = "dnwell"
layer_names["nimplant"]= "nsdm"
layer_names["pimplant"]= "psdm"
layer_names["vtl"] = "lvtn"
layer_names["vth"] = "hvtp"
layer_names["thkox"] = "thkox"
layer_names["poly"] = "poly"
layer_names["contact"] = "licon1"
layer_names["li"] = "li1"
layer_names["mcon"] = "mcon"
layer_names["m1"] = "met1"
layer_names["m1p"] = "met1"
layer_names["via1"] = "via"
layer_names["m2"] = "met2"
layer_names["m2p"] = "met2"
layer_names["via2"] = "via2"
layer_names["m3"] = "met3"
layer_names["m3p"] = "met3"
layer_names["via3"] = "via3"
layer_names["m4"] = "met4"
layer_names["m4p"] = "met4"
layer_names["via4"] = "via4"
layer_names["m5p"] = "met5"
layer_names["boundary"]= "boundary"
layer_names["stdc"] = "areaid.standardc"
layer_names["mem"] = "areaid.core"
layer_names["text"] = "text"
###################################################
# DRC/LVS Rules Setup
###################################################
# technology parameter
parameter={}
# difftap.2b
parameter["min_tx_size"] = 0.150
parameter["beta"] = 3
parameter["6T_inv_nmos_size"] = 0.205
parameter["6T_inv_pmos_size"] = 0.09
parameter["6T_access_size"] = 0.135
drc = d.design_rules("sky130")
# grid size
drc["grid"] = 0.005
#DRC/LVS test set_up
# Switching between calibre and magic can be useful for development,
# it eventually should be deleted.
NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False)
use_calibre = bool(NDA_PDK_ROOT)
use_calibre = False
use_klayout = False
if use_calibre:
# Correct order according to s8
pin_purpose = 16
label_purpose = 5
drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules"
drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts"
drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm"
drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap"
# minwidth_tx with contact (no dog bone transistors)
# difftap.2b
drc["minwidth_tx"] = 0.360
drc["minlength_channel"] = 0.150
drc["pwell_to_nwell"] = 0
# nwell.1 Minimum width of nwell/pwell
drc.add_layer("nwell",
width=0.840,
spacing=1.270)
# poly.1a Minimum width of poly
# poly.2 Minimum spacing of poly AND active
drc.add_layer("poly",
width=0.150,
spacing=0.210)
# poly.8
drc["poly_extend_active"] = 0.13
# Not a rule
drc["poly_to_contact"] = 0
# poly.7 Minimum enclosure of active around gate
drc["active_enclose_gate"] = 0.075
# poly.4 Minimum spacing of field poly to active
drc["poly_to_active"] = 0.075
# poly.2 Minimum spacing of field poly
drc["poly_to_field_poly"] = 0.210
# difftap.1 Minimum width of active
# difftap.3 Minimum spacing of active
drc.add_layer("active",
width=0.150,
spacing=0.270)
# difftap.8
drc.add_enclosure("nwell",
layer="active",
enclosure=0.18,
extension=0.18)
# nsd/psd.5a
drc.add_enclosure("implant",
layer="active",
enclosure=0.125)
# Same as active enclosure?
drc["implant_to_contact"] = 0.070
# nsd/psd.1 nsd/psd.2
drc.add_layer("implant",
width=0.380,
spacing=0.380,
area=0.265)
# licon.1, licon.2
drc.add_layer("contact",
width=0.170,
spacing=0.170)
# licon.5c (0.06 extension), (licon.7 for extension)
drc.add_enclosure("active",
layer="contact",
enclosure=0.040,
extension=0.060)
# licon.7
drc["tap_extend_contact"] = 0.120
# licon.8 Minimum enclosure of poly around contact
drc.add_enclosure("poly",
layer="contact",
enclosure=0.08,
extension=0.08)
# licon.11a
drc["active_contact_to_gate"] = 0.050
# npc.4 > licon.14 0.19 > licon.11a
drc["poly_contact_to_gate"] = 0.270
# licon.15
drc["npc_enclose_poly"] = 0.1
# li.1, li.3
drc.add_layer("li",
width=0.170,
spacing=0.170)
# licon.5
drc.add_enclosure("li",
layer="contact",
enclosure=0,
extension=0.080)
drc.add_enclosure("li",
layer="mcon",
enclosure=0,
extension=0.080)
# mcon.1, mcon.2
drc.add_layer("mcon",
width=0.170,
spacing=0.210)
# m1.1 Minimum width of metal1
# m1.2 Minimum spacing of metal1
# m1.6 Minimum area of metal1
drc.add_layer("m1",
width=0.140,
spacing=0.140,
area=0.083)
# m1.4 Minimum enclosure of metal1
# m1.5 Minimum enclosure around contact on two opposite sides
drc.add_enclosure("m1",
layer="mcon",
enclosure=0.030,
extension=0.060)
# via.4a Minimum enclosure around via1
# via.5a Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m1",
layer="via1",
enclosure=0.055,
extension=0.085)
# via.1a Minimum width of via1
# via.2 Minimum spacing of via1
drc.add_layer("via1",
width=0.150,
spacing=0.170)
# m2.1 Minimum width of intermediate metal
# m2.2 Minimum spacing of intermediate metal
# m2.6 Minimum area of metal2
drc.add_layer("m2",
width=0.140,
spacing=0.140,
area=0.0676)
# m2.4 Minimum enclosure around via1
# m2.5 Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m2",
layer="via1",
enclosure=0.055,
extension=0.085)
# via2.4 Minimum enclosure around via2
# via2.5 Minimum enclosure around via2 on two opposite sides
drc.add_enclosure("m2",
layer="via2",
enclosure=0.040,
extension=0.085)
# via2.1a Minimum width of Via2
# via2.2 Minimum spacing of Via2
drc.add_layer("via2",
width=0.200,
spacing=0.200)
# m3.1 Minimum width of metal3
# m3.2 Minimum spacing of metal3
# m3.6 Minimum area of metal3
drc.add_layer("m3",
width=0.300,
spacing=0.300,
area=0.240)
# m3.4 Minimum enclosure around via2
drc.add_enclosure("m3",
layer="via2",
enclosure=0.065)
# via3.4 Minimum enclosure around via3
# via3.5 Minimum enclosure around via3 on two opposite sides
drc.add_enclosure("m3",
layer="via3",
enclosure=0.060,
extension=0.090)
# via3.1 Minimum width of Via3
# via3.2 Minimum spacing of Via3
drc.add_layer("via3",
width=0.200,
spacing=0.200)
# m4.1 Minimum width of metal4
# m4.2 Minimum spacing of metal4
# m4.7 Minimum area of metal4
drc.add_layer("m4",
width=0.300,
spacing=0.300,
area=0.240)
# m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via3",
enclosure=0.065)
# FIXME: Wrong rule m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via4",
enclosure=0.060)
# via4.1 Minimum width of Via4
# via4.2 Minimum spacing of Via4
drc.add_layer("via4",
width=0.800,
spacing=0.800)
# FIXME: Wrong rules
# m5.1 Minimum width of metal5
# m5.2 Minimum spacing of metal5
# m5.7 Minimum area of metal5
drc.add_layer("m5",
width=1.600,
spacing=1.600,
area=4.000)
# m5.3 Minimum enclosure around via4
drc.add_enclosure("m5",
layer="via4",
enclosure=0.310)
# Metal 5-10 are ommitted
###################################################
# Spice Simulation Parameters
###################################################
# spice info
spice = {}
spice["nmos"] = "sky130_fd_pr__nfet_01v8"
spice["pmos"] = "sky130_fd_pr__pfet_01v8"
spice["power"]="vccd1"
spice["ground"]="vssd1"
# whether or not the device model is actually a subckt
spice["device_prefix"] = "X"
spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]],
"SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]],
"FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]],
"SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]],
"FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] }
# spice stimulus related variables
spice["feasible_period"] = 10 # estimated feasible period in ns
spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts]
spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts]
spice["rise_time"] = 0.005 # rise time in [Nano-seconds]
spice["fall_time"] = 0.005 # fall time in [Nano-seconds]
spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius)
spice["nom_temperature"] = 25 # Nominal temperature (celcius)
# analytical delay parameters
spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts
spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square
spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2
spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff
spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff
spice["dff_setup"] = 102.5391 # DFF setup time in ps
spice["dff_hold"] = -56 # DFF hold time in ps
spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad]
spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad]
# analytical power parameters, many values are temporary
spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW
spice["inv_leakage"] = 1 # Leakage power of inverter in nW
spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW
spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW
spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW
spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW
spice["dff_leakage"] = 1 # Leakage power of flop in nW
spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz
# Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["le_tau"] = 2.25 # In pico-seconds.
parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad
parameter["dff_clk_cin"] = 30.6 # relative capacitance
parameter["6tcell_wl_cin"] = 3 # relative capacitance
parameter["min_inv_para_delay"] = 2.4 # Tau delay units
parameter["sa_en_pmos_size"] = 0.72 # micro-meters
parameter["sa_en_nmos_size"] = 0.27 # micro-meters
parameter["sa_inv_pmos_size"] = 0.54 # micro-meters
parameter["sa_inv_nmos_size"] = 0.27 # micro-meters
parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance
###################################################
# Technology Tool Preferences
###################################################
if use_calibre:
drc_name = "calibre"
lvs_name = "calibre"
pex_name = "calibre"
elif use_klayout:
drc_name = "klayout"
lvs_name = "klayout"
pex_name = "klayout"
else:
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"
# This is used by uniqify to not rename the library cells
library_prefix_name = "sky130_fd_bd_sram__"
# List of cells to skip running DRC/LVS on directly
# This will look for a maglef file and copy it over the mag file
# before DRC after extraction
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs
flatglob = ["*_?mos_m*",
"sky130_fd_bd_sram__sram_sp_cell_fom_serifs",
"sky130_fd_bd_sram__sram_sp_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"]
blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell",
"sky130_fd_bd_sram__openram_dp_cell_dummy",
"sky130_fd_bd_sram__openram_dp_cell_replica",
"sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon",
"sky130_fd_bd_sram__openram_sp_colend_replica",
"sky130_fd_bd_sram__openram_sp_colenda_replica",
"sky130_fd_bd_sram__sram_sp_cell_opt1a",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy",
"sky130_fd_bd_sram__openram_sp_cell_opt1_dummy",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__sram_sp_cell_opt1",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica",
"sky130_fd_bd_sram__sram_sp_colend",
"sky130_fd_bd_sram__sram_sp_colend_cent",
"sky130_fd_bd_sram__sram_sp_colend_p_cent",
"sky130_fd_bd_sram__sram_sp_colenda",
"sky130_fd_bd_sram__sram_sp_colenda_cent",
"sky130_fd_bd_sram__sram_sp_colenda_p_cent",
"sky130_fd_bd_sram__sram_sp_rowend",
"sky130_fd_bd_sram__sram_sp_rowenda",
"sky130_fd_bd_sram__openram_sp_rowend_replica",
"sky130_fd_bd_sram__openram_sp_rowenda_replica",
"sky130_fd_bd_sram__sram_sp_corner",
"sky130_fd_bd_sram__sram_sp_cornera",
"sky130_fd_bd_sram__sram_sp_cornerb",
"sky130_fd_bd_sram__sram_sp_wlstrapa",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p"]

View File

@ -0,0 +1,849 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California
# All rights reserved.
#
import os
from openram import drc as d
"""
File containing the process technology parameters for Skywater 130nm.
"""
###################################################
# Custom modules
###################################################
# This uses the default classes to instantiate module from
# '$OPENRAM_HOME/compiler/modules'.
# Using tech_modules['cellname'] you can override each class by providing a custom
# implementation in '$OPENRAM_TECHDIR/modules/'
# For example: tech_modules["contact"] = "contact_freepdk45"
tech_modules = d.module_type()
# These modules have been hand designed and provided in this repository.
tech_modules["nand2_dec"] = "nand2_dec"
tech_modules["nand3_dec"] = "nand3_dec"
tech_modules["nand4_dec"] = "nand4_dec"
# Override default OpenRAM modules to sky130 modules
# These are for single port and dual port as a list,
# or for both if there is no list,
# or only applicable to one if there is no list.
tech_modules["bitcell_1port"] = "sky130_bitcell"
tech_modules["replica_bitcell_1port"] = "sky130_replica_bitcell"
tech_modules["dummy_bitcell_1port"] = "sky130_dummy_bitcell"
tech_modules["replica_bitcell_2port"] = "replica_bitcell_2port"
tech_modules["dummy_bitcell_2port"] = "dummy_bitcell_2port"
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"]
tech_modules["col_cap_array"] = ["sky130_col_cap_array", "col_cap_array"]
tech_modules["col_cap"] = ["sky130_col_cap", "col_cap_bitcell_2port"]
tech_modules["corner"] = ["sky130_corner", None]
tech_modules["internal"] = ["sky130_internal", None]
tech_modules["row_cap_array"] = ["sky130_row_cap_array", "row_cap_array"]
tech_modules["row_cap"] = ["sky130_row_cap", "row_cap_bitcell_2port"]
# These modules are auto-generated from the nand decoders above and are not
# found in this.
tech_modules["buf_dec"] = "pbuf_dec"
tech_modules["inv_dec"] = "pinv_dec"
tech_modules["and2_dec"] = "and2_dec"
tech_modules["and3_dec"] = "and3_dec"
tech_modules["and4_dec"] = "and4_dec"
###################################################
# Custom cell properties
###################################################
cell_properties = d.cell_properties()
cell_properties.bitcell_power_pin_directions = ("H", "H")
cell_properties.bitcell_1port.mirror.x = True
cell_properties.bitcell_1port.mirror.y = True
cell_properties.bitcell_1port.end_caps = True
cell_properties.bitcell_1port.has_corners = True
cell_properties.bitcell_1port.boundary_layer = "mem"
cell_properties.bitcell_1port.port_order = ['bl', 'br', 'gnd', 'vdd', 'vpb', 'vnb', 'wl']
cell_properties.bitcell_1port.port_types = ["OUTPUT", "OUTPUT", "GROUND", "POWER", "BIAS", "BIAS", "INPUT"]
cell_properties.bitcell_1port.port_map = {'bl': 'BL',
'br': 'BR',
'wl': 'WL',
'vdd': 'VPWR',
'vnb': 'VNB',
'vpb': 'VPB',
'gnd': 'VGND'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.bl_layer = "m1"
cell_properties.bitcell_1port.vdd_layer = "m1"
cell_properties.bitcell_1port.vdd_dir = "V"
cell_properties.bitcell_1port.gnd_layer = "m2"
cell_properties.bitcell_1port.gnd_dir = "H"
cell_properties.bitcell_2port.mirror.x = True
cell_properties.bitcell_2port.mirror.y = True
cell_properties.bitcell_2port.end_caps = True
cell_properties.bitcell_2port.has_corners = False
cell_properties.bitcell_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'wl0', 'wl1', 'vdd', 'gnd']
cell_properties.bitcell_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'wl0': 'WL0',
'wl1': 'WL1',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.bitcell_1port.wl_layer = "m2"
cell_properties.bitcell_1port.vdd_layer = "m2"
cell_properties.bitcell_1port.vdd_dir = "H"
cell_properties.bitcell_1port.gnd_layer = "m2"
cell_properties.bitcell_1port.gnd_dir = "H"
cell_properties.bitcell_2port.wl_layer = "m2"
cell_properties.bitcell_2port.vdd_layer = "m1"
cell_properties.bitcell_2port.vdd_dir = "H"
cell_properties.bitcell_2port.gnd_layer = "m2"
cell_properties.bitcell_2port.gnd_dir = "H"
cell_properties.col_cap_1port_bitcell = d.cell(['bl', 'br', 'vdd', 'gnd', 'vpb', 'vnb', 'gate'],
['INPUT', 'INPUT','POWER', 'GROUND', 'BIAS', 'BIAS', 'INPUT'],
{'bl': 'bl',
'br': 'br',
'vdd': 'vdd',
'gnd': 'gnd',
'vnb': 'vnb',
'vpb': 'vpb',
'gate': 'gate'})
cell_properties.col_cap_1port_bitcell.boundary_layer = "mem"
cell_properties.col_cap_1port_strap_power = d.cell(['vdd', 'vpb', 'vnb'],
['POWER', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'vdd': 'VPWR'})
cell_properties.col_cap_1port_strap_power.boundary_layer = "mem"
cell_properties.col_cap_1port_strap_ground = d.cell(['gnd', 'vpb', 'vnb'],
['GROUND', 'BIAS', 'BIAS'],
{'vnb': 'VNB',
'vpb': 'VPB',
'gnd': 'VGND'})
cell_properties.col_cap_1port_strap_ground.boundary_layer = "mem"
cell_properties.row_cap_1port_cell = d.cell(['vdd', 'wl'],
['POWER', 'INPUT'],
{'wl': 'WL',
'vdd': 'VPWR'})
cell_properties.row_cap_1port_cell.boundary_layer = "mem"
cell_properties.col_cap_2port.port_order = ['bl0', 'br0', 'bl1', 'br1', 'vdd']
cell_properties.col_cap_2port.port_map = {'bl0': 'BL0',
'br0': 'BR0',
'bl1': 'BL1',
'br1': 'BR1',
'vdd': 'VDD'}
cell_properties.row_cap_2port.port_order = ['wl0', 'wl1', 'gnd']
cell_properties.row_cap_2port.port_map = {'wl0': 'WL0',
'wl1': 'WL1',
'gnd': 'GND'}
cell_properties.ptx.bin_spice_models = True
cell_properties.ptx.model_is_subckt = True
cell_properties.pgate.add_implants = True
cell_properties.use_strap = True
cell_properties.strap_module = "internal"
cell_properties.strap_version = "wlstrap"
cell_properties.dff.port_order = ['D', 'Q', 'clk', 'vdd', 'gnd']
cell_properties.dff.port_map = {'D': 'D',
'Q': 'Q',
'clk': 'CLK',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand2_dec.port_order = ['A', 'B', 'Z', 'vdd', 'gnd']
cell_properties.nand2_dec.port_map = {'A': 'A',
'B': 'B',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand3_dec.port_order = ['A', 'B', 'C', 'Z', 'vdd', 'gnd']
cell_properties.nand3_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.nand4_dec.port_order = ['A', 'B', 'C', 'D', 'Z', 'vdd', 'gnd']
cell_properties.nand4_dec.port_map = {'A': 'A',
'B': 'B',
'C': 'C',
'D': 'D',
'Z': 'Z',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.sense_amp.port_order = ['bl', 'br', 'dout', 'en', 'vdd', 'gnd']
cell_properties.sense_amp.port_map = {'bl': 'BL',
'br': 'BR',
'dout': 'DOUT',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
cell_properties.write_driver.port_order = ['din', 'bl', 'br', 'en', 'vdd', 'gnd']
cell_properties.write_driver.port_map = {'din': 'DIN',
'bl': 'BL',
'br': 'BR',
'en': 'EN',
'vdd': 'VDD',
'gnd': 'GND'}
# You can override the GDS for custom cell using the following:
# If it is a list, the first is single port and the second is dual port.
# If it is string, it is used for both single and dual port.
cell_properties.names["dff"] = "sky130_fd_bd_sram__openram_dff"
cell_properties.names["nand2_dec"] = ["sky130_fd_bd_sram__openram_sp_nand2_dec", "sky130_fd_bd_sram__openram_dp_nand2_dec"]
cell_properties.names["nand3_dec"] = ["sky130_fd_bd_sram__openram_sp_nand3_dec", "sky130_fd_bd_sram__openram_dp_nand3_dec"]
cell_properties.names["nand4_dec"] = ["sky130_fd_bd_sram__openram_sp_nand4_dec", "sky130_fd_bd_sram__openram_dp_nand4_dec"]
cell_properties.names["bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell"
cell_properties.names["dummy_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_dummy"
cell_properties.names["replica_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_replica"
cell_properties.names["col_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_col"
cell_properties.names["row_cap_bitcell_2port"] = "sky130_fd_bd_sram__openram_dp_cell_cap_row"
cell_properties.names["sense_amp"] = "sky130_fd_bd_sram__openram_sense_amp"
cell_properties.names["write_driver"] = "sky130_fd_bd_sram__openram_write_driver"
array_row_multiple = 2
array_col_multiple = 2
###################################################
# Custom layer properties
###################################################
layer_properties = d.layer_properties()
layer_properties.hierarchical_decoder.bus_layer = "m1"
layer_properties.hierarchical_decoder.bus_directions = "nonpref"
layer_properties.hierarchical_decoder.input_layer = "li"
layer_properties.hierarchical_decoder.output_layer = "m2"
layer_properties.hierarchical_decoder.vertical_supply = True
layer_properties.hierarchical_predecode.bus_layer = "m1"
layer_properties.hierarchical_predecode.bus_directions = "nonpref"
# This is added to allow the column decoder connections on m2
layer_properties.hierarchical_predecode.bus_pitch_factor = 1.2
layer_properties.hierarchical_predecode.bus_space_factor = 1.5
layer_properties.hierarchical_predecode.input_layer = "li"
layer_properties.hierarchical_predecode.output_layer = "m2"
layer_properties.hierarchical_predecode.vertical_supply = True
layer_properties.hierarchical_predecode.force_horizontal_input_contact = True
layer_properties.bank.stack = "m2_stack"
layer_properties.bank.pitch = "m3_pitch"
layer_properties.column_mux_array.select_layer = "m3"
layer_properties.column_mux_array.bitline_layer = "m1"
layer_properties.port_address.supply_offset = True
layer_properties.port_data.enable_layer = "m1"
layer_properties.port_data.channel_route_bitlines = False
layer_properties.replica_column.even_rows = True
layer_properties.wordline_driver.vertical_supply = True
layer_properties.global_wordline_layer = "m5"
###################################################
# Discrete tx bins
###################################################
# enforce that tx sizes are within 25% of requested size after fingering.
accuracy_requirement = 0.75
nmos_bins = {
0.15 : [0.36, 0.39, 0.42, 0.52, 0.54, 0.55, 0.58, 0.6, 0.61, 0.64, 0.65, 0.74, 0.84, 1.0, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.18 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
0.25 : [0.65, 1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 0.65, 1.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42, 0.65, 1.0, 3.0, 5.0, 7.0]
}
pmos_bins = {
0.15 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.65, 1.68, 2.0, 3.0, 5.0, 7.0],
1.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
2.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
4.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
8.0 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
0.17 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12],
0.18 : [0.42, 0.55, 0.64, 0.84, 1.0, 1.12, 1.26, 1.68, 2.0, 3.0, 5.0, 7.0],
0.25 : [1.0, 3.0, 5.0, 7.0],
0.5 : [0.42, 0.55, 1.0, 3.0, 5.0, 7.0],
20.0 : [0.42]
}
###################################################
# GDS file info
###################################################
GDS = {}
# gds units
# From http://www.cnf.cornell.edu/cnf_spie9.html: "The first
# is the size of a database unit in user units. The second is the size
# of a database unit in meters. For example, if your library was
# created with the default units (user unit = 1 um and 1000 database
# units per user unit), then the first number would be 0.001 and the
# second number would be 10-9. Typically, the first number is less than
# 1, since you use more than 1 database unit per user unit. To
# calculate the size of a user unit in meters, divide the second number
# by the first."
GDS["unit"] = (0.001, 1e-9)
#GDS["unit"]=(0.001, 1e-6)
###################################################
# Interconnect stacks
###################################################
poly_stack = ("poly", "contact", "li")
active_stack = ("active", "contact", "li")
li_stack = ("li", "mcon", "m1")
m1_stack = ("m1", "via1", "m2")
m2_stack = ("m2", "via2", "m3")
m3_stack = ("m3", "via3", "m4")
m4_stack = ("m4", "via4", "m5")
lef_rom_interconnect = ["m1", "m2", "m3", "m4"]
layer_indices = {"poly": 0,
"active": 0,
"nwell": 0,
"li": 1,
"m1": 2,
"m2": 3,
"m3": 4,
"m4": 5,
"m5": 6}
# The FEOL stacks get us up to m1
feol_stacks = [poly_stack,
active_stack,
li_stack]
# The BEOL stacks are m1 and up
beol_stacks = [m1_stack,
m2_stack,
m3_stack,
m4_stack]
layer_stacks = feol_stacks + beol_stacks
preferred_directions = {"poly": "V",
"active": "V",
"li": "V",
"m1": "H",
"m2": "V",
"m3": "H",
"m4": "V",
"m5": "H"}
###################################################
# GDS Layer Map
###################################################
layer = {}
layer["active"] = (65, 20) # diff
layer["activep"] = (65, 20) # diff
layer["tap"] = (65, 44) # tap
layer["pwellp"] = (122,16)
layer["nwell"] = (64, 20) # nwell
layer["dnwell"] = (64,18)
layer["nimplant"]= (93, 44) # nsdm
layer["pimplant"]= (94, 20) # psdm
layer["vtl"] = (125, 44) # lvtn
layer["vth"] = (78, 44) # hvtp (pmos only)
layer["thkox"] = (8, 0)
layer["poly"] = (66, 20)
layer["contact"] = (66, 44) # licon1
layer["npc"] = (95, 20) # npc (nitride cut)
layer["li"] = (67, 20) # active li1
layer["mcon"] = (67, 44) # mcon
layer["m1"] = (68, 20) # met1
layer["m1p"] = (68, 5) # met1 pin
layer["via1"] = (68, 44) # via1
layer["m2"] = (69, 20) # met2
layer["m2p"] = (69, 5) # met2 pin
layer["via2"] = (69, 44) # via2
layer["m3"] = (70, 20) # met3
layer["m3p"] = (70, 5) # met3 pin
layer["via3"] = (70, 44) # via3
layer["m4"] = (71, 20) # met4
layer["m4p"] = (71, 5) # met4 pin
layer["via4"] = (71, 44) # via4
layer["m5"] = (72, 20) # met5
layer["m5p"] = (72, 5) # met5 pin
layer["boundary"]= (235, 4)
# specific boundary type to define standard cell regions for DRC
layer["stdc"] = (81, 4)
layer["mem"] = (81, 2)
# Not an official sky130 layer, but useful for router debug infos
layer["text"]= (234, 5)
# Excpected value according to sky130A tech file
# If calibre is enabled, these will be swapped below
#pin_purpose = 5
label_purpose = 5
#label_purpose = 16
#pin_purpose = 16
#label_purpose = 5
# pin_read purposes
special_purposes = {layer["nwell"][0]: [layer["nwell"][1], 5, 59, 16]}
#layer_override = {"VNB\x00": ["pwell",122]}
layer_override = {"vnb": layer["pwellp"], "VNB": layer["pwellp"]}
layer_override_name = {"vnb": "pwellp", "VNB": "pwellp"}
layer_override_purpose = {122: (64, 59)}
# Layer names for external PDKs
layer_names = {}
layer_names["active"] = "diff"
layer_names["activep"] = "diff"
layer_names["tap"] = "tap"
layer_names["pwellp"] = "pwellp"
layer_names["nwell"] = "nwell"
layer_names["dnwell"] = "dnwell"
layer_names["nimplant"]= "nsdm"
layer_names["pimplant"]= "psdm"
layer_names["vtl"] = "lvtn"
layer_names["vth"] = "hvtp"
layer_names["thkox"] = "thkox"
layer_names["poly"] = "poly"
layer_names["contact"] = "licon1"
layer_names["li"] = "li1"
layer_names["mcon"] = "mcon"
layer_names["m1"] = "met1"
layer_names["m1p"] = "met1"
layer_names["via1"] = "via"
layer_names["m2"] = "met2"
layer_names["m2p"] = "met2"
layer_names["via2"] = "via2"
layer_names["m3"] = "met3"
layer_names["m3p"] = "met3"
layer_names["via3"] = "via3"
layer_names["m4"] = "met4"
layer_names["m4p"] = "met4"
layer_names["via4"] = "via4"
layer_names["m5p"] = "met5"
layer_names["boundary"]= "boundary"
layer_names["stdc"] = "areaid.standardc"
layer_names["mem"] = "areaid.core"
layer_names["text"] = "text"
###################################################
# DRC/LVS Rules Setup
###################################################
# technology parameter
parameter={}
# difftap.2b
parameter["min_tx_size"] = 0.150
parameter["beta"] = 3
parameter["6T_inv_nmos_size"] = 0.205
parameter["6T_inv_pmos_size"] = 0.09
parameter["6T_access_size"] = 0.135
drc = d.design_rules("sky130")
# grid size
drc["grid"] = 0.005
#DRC/LVS test set_up
# Switching between calibre and magic can be useful for development,
# it eventually should be deleted.
NDA_PDK_ROOT = os.environ.get("NDA_PDK_ROOT", False)
use_calibre = bool(NDA_PDK_ROOT)
use_calibre = False
use_klayout = False
if use_calibre:
# Correct order according to s8
pin_purpose = 16
label_purpose = 5
drc["drc_rules"] = NDA_PDK_ROOT + "/DRC/Calibre/s8_drcRules"
drc["lvs_rules"] = NDA_PDK_ROOT + "/LVS/Calibre/lvs_s8_opts"
drc["xrc_rules"] = NDA_PDK_ROOT + "/PEX/xRC/extLvsRules_s8_5lm"
drc["layer_map"] = NDA_PDK_ROOT + "/VirtuosoOA/libs/technology_library/s8phirs_10r.layermap"
# minwidth_tx with contact (no dog bone transistors)
# difftap.2b
drc["minwidth_tx"] = 0.360
drc["minlength_channel"] = 0.150
drc["pwell_to_nwell"] = 0
# nwell.1 Minimum width of nwell/pwell
drc.add_layer("nwell",
width=0.840,
spacing=1.270)
# poly.1a Minimum width of poly
# poly.2 Minimum spacing of poly AND active
drc.add_layer("poly",
width=0.150,
spacing=0.210)
# poly.8
drc["poly_extend_active"] = 0.13
# Not a rule
drc["poly_to_contact"] = 0
# poly.7 Minimum enclosure of active around gate
drc["active_enclose_gate"] = 0.075
# poly.4 Minimum spacing of field poly to active
drc["poly_to_active"] = 0.075
# poly.2 Minimum spacing of field poly
drc["poly_to_field_poly"] = 0.210
# difftap.1 Minimum width of active
# difftap.3 Minimum spacing of active
drc.add_layer("active",
width=0.150,
spacing=0.270)
# difftap.8
drc.add_enclosure("nwell",
layer="active",
enclosure=0.18,
extension=0.18)
# nsd/psd.5a
drc.add_enclosure("implant",
layer="active",
enclosure=0.125)
# Same as active enclosure?
drc["implant_to_contact"] = 0.070
# nsd/psd.1 nsd/psd.2
drc.add_layer("implant",
width=0.380,
spacing=0.380,
area=0.265)
# licon.1, licon.2
drc.add_layer("contact",
width=0.170,
spacing=0.170)
# licon.5c (0.06 extension), (licon.7 for extension)
drc.add_enclosure("active",
layer="contact",
enclosure=0.040,
extension=0.060)
# licon.7
drc["tap_extend_contact"] = 0.120
# licon.8 Minimum enclosure of poly around contact
drc.add_enclosure("poly",
layer="contact",
enclosure=0.08,
extension=0.08)
# licon.11a
drc["active_contact_to_gate"] = 0.050
# npc.4 > licon.14 0.19 > licon.11a
drc["poly_contact_to_gate"] = 0.270
# licon.15
drc["npc_enclose_poly"] = 0.1
# li.1, li.3
drc.add_layer("li",
width=0.170,
spacing=0.170)
# licon.5
drc.add_enclosure("li",
layer="contact",
enclosure=0,
extension=0.080)
drc.add_enclosure("li",
layer="mcon",
enclosure=0,
extension=0.080)
# mcon.1, mcon.2
drc.add_layer("mcon",
width=0.170,
spacing=0.210)
# m1.1 Minimum width of metal1
# m1.2 Minimum spacing of metal1
# m1.6 Minimum area of metal1
drc.add_layer("m1",
width=0.140,
spacing=0.140,
area=0.083)
# m1.4 Minimum enclosure of metal1
# m1.5 Minimum enclosure around contact on two opposite sides
drc.add_enclosure("m1",
layer="mcon",
enclosure=0.030,
extension=0.060)
# via.4a Minimum enclosure around via1
# via.5a Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m1",
layer="via1",
enclosure=0.055,
extension=0.085)
# via.1a Minimum width of via1
# via.2 Minimum spacing of via1
drc.add_layer("via1",
width=0.150,
spacing=0.170)
# m2.1 Minimum width of intermediate metal
# m2.2 Minimum spacing of intermediate metal
# m2.6 Minimum area of metal2
drc.add_layer("m2",
width=0.140,
spacing=0.140,
area=0.0676)
# m2.4 Minimum enclosure around via1
# m2.5 Minimum enclosure around via1 on two opposite sides
drc.add_enclosure("m2",
layer="via1",
enclosure=0.055,
extension=0.085)
# via2.4 Minimum enclosure around via2
# via2.5 Minimum enclosure around via2 on two opposite sides
drc.add_enclosure("m2",
layer="via2",
enclosure=0.040,
extension=0.085)
# via2.1a Minimum width of Via2
# via2.2 Minimum spacing of Via2
drc.add_layer("via2",
width=0.200,
spacing=0.200)
# m3.1 Minimum width of metal3
# m3.2 Minimum spacing of metal3
# m3.6 Minimum area of metal3
drc.add_layer("m3",
width=0.300,
spacing=0.300,
area=0.240)
# m3.4 Minimum enclosure around via2
drc.add_enclosure("m3",
layer="via2",
enclosure=0.065)
# via3.4 Minimum enclosure around via3
# via3.5 Minimum enclosure around via3 on two opposite sides
drc.add_enclosure("m3",
layer="via3",
enclosure=0.060,
extension=0.090)
# via3.1 Minimum width of Via3
# via3.2 Minimum spacing of Via3
drc.add_layer("via3",
width=0.200,
spacing=0.200)
# m4.1 Minimum width of metal4
# m4.2 Minimum spacing of metal4
# m4.7 Minimum area of metal4
drc.add_layer("m4",
width=0.300,
spacing=0.300,
area=0.240)
# m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via3",
enclosure=0.065)
# FIXME: Wrong rule m4.3 Minimum enclosure around via3
drc.add_enclosure("m4",
layer="via4",
enclosure=0.060)
# via4.1 Minimum width of Via4
# via4.2 Minimum spacing of Via4
drc.add_layer("via4",
width=0.800,
spacing=0.800)
# FIXME: Wrong rules
# m5.1 Minimum width of metal5
# m5.2 Minimum spacing of metal5
# m5.7 Minimum area of metal5
drc.add_layer("m5",
width=1.600,
spacing=1.600,
area=4.000)
# m5.3 Minimum enclosure around via4
drc.add_enclosure("m5",
layer="via4",
enclosure=0.310)
# Metal 5-10 are ommitted
###################################################
# Spice Simulation Parameters
###################################################
# spice info
spice = {}
spice["nmos"] = "sky130_fd_pr__nfet_01v8"
spice["pmos"] = "sky130_fd_pr__pfet_01v8"
spice["power"]="vccd1"
spice["ground"]="vssd1"
# whether or not the device model is actually a subckt
spice["device_prefix"] = "X"
spice["fet_libraries"] = { "TT": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "tt"]],
"SS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ss"]],
"FF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "ff"]],
"SF": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "sf"]],
"FS": [[os.environ.get("SPICE_MODEL_DIR") + "/sky130.lib.spice", "fs"]] }
# spice stimulus related variables
spice["feasible_period"] = 10 # estimated feasible period in ns
spice["supply_voltages"] = [1.7, 1.8, 1.9] # Supply voltage corners in [Volts]
spice["nom_supply_voltage"] = 1.8 # Nominal supply voltage in [Volts]
spice["rise_time"] = 0.005 # rise time in [Nano-seconds]
spice["fall_time"] = 0.005 # fall time in [Nano-seconds]
spice["temperatures"] = [0, 25, 100] # Temperature corners (celcius)
spice["nom_temperature"] = 25 # Nominal temperature (celcius)
# analytical delay parameters
spice["nom_threshold"] = 0.49 # Typical Threshold voltage in Volts
spice["wire_unit_r"] = 0.125 # Unit wire resistance in ohms/square
spice["wire_unit_c"] = 0.134 # Unit wire capacitance ff/um^2
spice["min_tx_drain_c"] = 0.7 # Minimum transistor drain capacitance in ff
spice["min_tx_gate_c"] = 0.2 # Minimum transistor gate capacitance in ff
spice["dff_setup"] = 102.5391 # DFF setup time in ps
spice["dff_hold"] = -56 # DFF hold time in ps
spice["dff_in_cap"] = 6.89 # Input capacitance (D) [Femto-farad]
spice["dff_out_cap"] = 6.89 # Output capacitance (Q) [Femto-farad]
# analytical power parameters, many values are temporary
spice["bitcell_leakage"] = 1 # Leakage power of a single bitcell in nW
spice["inv_leakage"] = 1 # Leakage power of inverter in nW
spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW
spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW
spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW
spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW
spice["dff_leakage"] = 1 # Leakage power of flop in nW
spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz
# Parameters related to sense amp enable timing and delay chain/RBL sizing
parameter["le_tau"] = 2.25 # In pico-seconds.
parameter["cap_relative_per_ff"] = 7.5 # Units of Relative Capacitance/ Femto-Farad
parameter["dff_clk_cin"] = 30.6 # relative capacitance
parameter["6tcell_wl_cin"] = 3 # relative capacitance
parameter["min_inv_para_delay"] = 2.4 # Tau delay units
parameter["sa_en_pmos_size"] = 0.72 # micro-meters
parameter["sa_en_nmos_size"] = 0.27 # micro-meters
parameter["sa_inv_pmos_size"] = 0.54 # micro-meters
parameter["sa_inv_nmos_size"] = 0.27 # micro-meters
parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of drain capacitance
###################################################
# Technology Tool Preferences
###################################################
if use_calibre:
drc_name = "calibre"
lvs_name = "calibre"
pex_name = "calibre"
elif use_klayout:
drc_name = "klayout"
lvs_name = "klayout"
pex_name = "klayout"
else:
drc_name = "magic"
lvs_name = "netgen"
pex_name = "magic"
# This is used by uniqify to not rename the library cells
library_prefix_name = "sky130_fd_bd_sram__"
# List of cells to skip running DRC/LVS on directly
# This will look for a maglef file and copy it over the mag file
# before DRC after extraction
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell
# gds flatglob sky130_fd_bd_sram__openram_sp_cell_opt1a_cell
# gds flatglob sky130_fd_bd_sram__sram_sp_cell_fom_serifs
flatglob = ["*_?mos_m*",
"sky130_fd_bd_sram__sram_sp_cell_fom_serifs",
"sky130_fd_bd_sram__sram_sp_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_cell",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_cell",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica_ce",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce"]
blackbox_cells = ["sky130_fd_bd_sram__openram_dp_cell",
"sky130_fd_bd_sram__openram_dp_cell_dummy",
"sky130_fd_bd_sram__openram_dp_cell_replica",
"sky130_fd_bd_sram__openram_sp_cell_opt1_noblcon",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_noblcon",
"sky130_fd_bd_sram__openram_sp_colend_replica",
"sky130_fd_bd_sram__openram_sp_colenda_replica",
"sky130_fd_bd_sram__sram_sp_cell_opt1a",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy",
"sky130_fd_bd_sram__openram_sp_cell_opt1_dummy",
"sky130_fd_bd_sram__sram_sp_cell_opt1_ce",
"sky130_fd_bd_sram__sram_sp_cell_opt1",
"sky130_fd_bd_sram__openram_sp_cell_opt1_replica",
"sky130_fd_bd_sram__openram_sp_cell_opt1a_replica",
"sky130_fd_bd_sram__sram_sp_colend",
"sky130_fd_bd_sram__sram_sp_colend_cent",
"sky130_fd_bd_sram__sram_sp_colend_p_cent",
"sky130_fd_bd_sram__sram_sp_colenda",
"sky130_fd_bd_sram__sram_sp_colenda_cent",
"sky130_fd_bd_sram__sram_sp_colenda_p_cent",
"sky130_fd_bd_sram__sram_sp_rowend",
"sky130_fd_bd_sram__sram_sp_rowenda",
"sky130_fd_bd_sram__openram_sp_rowend_replica",
"sky130_fd_bd_sram__openram_sp_rowenda_replica",
"sky130_fd_bd_sram__sram_sp_corner",
"sky130_fd_bd_sram__sram_sp_cornera",
"sky130_fd_bd_sram__sram_sp_cornerb",
"sky130_fd_bd_sram__sram_sp_wlstrapa",
"sky130_fd_bd_sram__sram_sp_wlstrap_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap",
"sky130_fd_bd_sram__sram_sp_wlstrap_p_ce",
"sky130_fd_bd_sram__sram_sp_wlstrap_p",
"sky130_fd_bd_sram__sram_sp_wlstrapa_p"]