Initial commit of sky130 config files

This commit is contained in:
mrg 2021-08-18 11:21:52 -07:00
parent dfbf0ba6e1
commit fa2232fc11
19 changed files with 2569 additions and 0 deletions

2
.gitignore vendored
View File

@ -10,4 +10,6 @@
**/model_data
outputs
technology/freepdk45/ncsu_basekit
technology/sky130/*_lib
technology/sky130/tech/.magicrc
.idea

139
Makefile Normal file
View File

@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
#
# Copyright 2020 Regents of the University of California
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# The top directory where environment will be created.
TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))
.DEFAULT_GOAL := all
# Skywater PDK SRAM library
SRAM_LIBRARY ?= $(PDK_ROOT)/skywater-pdk/libraries/sky130_fd_bd_sram
# Open PDKs
OPEN_PDKS ?= $(PDK_ROOT)/sky130A
# Create lists of all the files to copy/link
GDS_FILES := $(sort $(wildcard $(SRAM_LIBRARY)/cells/*/*.gds))
MAG_FILES := $(sort $(wildcard $(SRAM_LIBRARY)/cells/*/*.mag))
SPICE_SUFFIX := spice
SPICE_LVS_SUFFIX := lvs.$(SPICE_SUFFIX)
SPICE_CALIBRE_SUFFIX := lvs.calibre.$(SPICE_SUFFIX)
SPICE_BASE_SUFFIX := base.$(SPICE_SUFFIX)
ALL_SPICE_FILES := $(sort $(wildcard $(SRAM_LIBRARY)/cells/*/*.$(SPICE_SUFFIX)))
MAGLEF_SUFFIX := maglef
MAGLEF_FILES := $(sort $(wildcard $(SRAM_LIBRARY)/cells/*/*.$(MAGLEF_SUFFIX)))
MAGICRC_FILE := $(OPEN_PDKS)/libs.tech/magic/sky130A.magicrc
ALL_FILES := $(ALL_SPICE_FILES) $(GDS_FILES) $(MAG_FILES) $(MAGLEF_FILES)
INSTALL_BASE_DIRS := gds_lib mag_lib sp_lib lvs_lib calibre_lvs_lib lef_lib maglef_lib
INSTALL_BASE := $(OPENRAM_HOME)/../technology/sky130
INSTALL_DIRS := $(addprefix $(INSTALL_BASE)/,$(INSTALL_BASE_DIRS))
all: $(INSTALL_DIRS)
@echo "Installing sky130 SRAM PDK..."
@echo "PDK_ROOT='$(PDK_ROOT)'"
@echo "SRAM_LIBRARY='$(SRAM_LIBRARY)'"
@echo "OPEN_PDKS='$(OPEN_PDKS)'"
@true
.PHONY: $(INSTALL_DIRS)
$(INSTALL_BASE)/gds_lib: $(GDS_FILES)
@echo
@echo "Setting up GDS cell library for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@cp -va $? $@
@echo "=================================================================="
@echo
$(INSTALL_BASE)/mag_lib: $(MAG_FILES)
@echo
@echo "Setting up MAG files for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@cp -va $? $@
@echo
cp -f $(MAGICRC_FILE) $(INSTALL_BASE)/tech/.magicrc
cp -f $(MAGICRC_FILE) $(INSTALL_BASE)/mag_lib/.magicrc
@echo "=================================================================="
@echo
$(INSTALL_BASE)/maglef_lib: $(MAGLEF_FILES)
@echo
@echo "Setting up MAGLEF cell library for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@for SP in $?; do \
cp -va $$SP $@/$$(basename $$SP .$(MAGLEF_SUFFIX)).mag; \
done
@echo
cp -f $(MAGICRC_FILE) $(INSTALL_BASE)/maglef_lib/.magicrc
@echo "=================================================================="
@echo
$(INSTALL_BASE)/lvs_lib: $(filter %.$(SPICE_LVS_SUFFIX),$(ALL_SPICE_FILES))
@echo
@echo "Setting up LVS cell library for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@for SP in $?; do \
cp -va $$SP $@/$$(basename $$SP .$(SPICE_LVS_SUFFIX)).sp; \
done
@echo "=================================================================="
@echo
$(INSTALL_BASE)/calibre_lvs_lib: $(filter %.$(SPICE_CALIBRE_SUFFIX),$(ALL_SPICE_FILES))
@echo
@echo "Setting up Calibre LVS library for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@for SP in $?; do \
cp -va $$SP $@/$$(basename $$SP .$(SPICE_CALIBRE_SUFFIX)).sp; \
done
@echo "=================================================================="
@echo
$(INSTALL_BASE)/sp_lib: $(filter-out %.$(SPICE_LVS_SUFFIX) %.$(SPICE_CALIBRE_SUFFIX),$(ALL_SPICE_FILES))
@echo
@echo "Setting up spice simulation library for OpenRAM."
@echo "=================================================================="
mkdir -p $@
@for SP in $(filter-out %.$(SPICE_BASE_SUFFIX),$?); do \
cp -va $$SP $@/$$(basename $$SP .$(SPICE_SUFFIX)).sp; \
done
@echo
@echo "Overwriting some cells with base version."
@for SP in $(filter %.$(SPICE_BASE_SUFFIX),$?); do \
cp -va $$SP $@/$$(basename $$SP .$(SPICE_BASE_SUFFIX)).sp; \
done
@echo "=================================================================="
@echo
clean:
rm -f $(INSTALL_BASE)/tech/.magicrc
rm -f $(INSTALL_BASE)/mag_lib/.magicrc
rm -f $(INSTALL_BASE)/lef_lib/.magicrc
rm -f $(INSTALL_BASE)/maglef_lib/.magicrc
rm -rf $(INSTALL_DIRS)

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
"""
This type of setup script should be placed in the setup_scripts directory in
the trunk
"""
import os
TECHNOLOGY = "sky130"
os.environ["MGC_TMPDIR"] = "/tmp"
###########################
# OpenRAM Paths
# OpenPDK needed for magicrc, tech file and spice models of transistors
if 'PDK_ROOT' in os.environ:
open_pdks = os.path.join(os.environ['PDK_ROOT'], 'sky130A', 'libs.tech')
else:
raise SystemError("Unable to find open_pdks tech file. Set PDK_ROOT.")
spice_model_dir = os.path.join(open_pdks, "SIMULATOR",)
sky130_lib_ngspice = os.path.join(open_pdks, "ngspice", "sky130.lib.spice")
# We may end up using Xyce but check if at least ngspice exists
if not os.path.exists(sky130_lib_ngspice):
raise SystemError("Did not find {} under {}".format(sky130_lib_ngspice, open_pdks))
os.environ["SPICE_MODEL_DIR"] = spice_model_dir
open_pdks = os.path.abspath(open_pdks)
sky130_magicrc = os.path.join(open_pdks, 'magic', "sky130A.magicrc")
if not os.path.exists(sky130_magicrc):
raise SystemError("Did not find {} under {}".format(sky130_magicrc, open_pdks))
os.environ["OPENRAM_MAGICRC"] = sky130_magicrc
sky130_netgenrc = os.path.join(open_pdks, 'netgen', "setup.tcl")
if not os.path.exists(sky130_netgenrc):
raise SystemError("Did not find {} under {}".format(sky130_netgenrc, open_pdks))
os.environ["OPENRAM_NETGENRC"] = sky130_netgenrc
try:
DRCLVS_HOME = os.path.abspath(os.environ.get("DRCLVS_HOME"))
except:
DRCLVS_HOME= "not-found"
os.environ["DRCLVS_HOME"] = DRCLVS_HOME

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
from tech import cell_properties as props
import bitcell_base
class sky130_bitcell(bitcell_base.bitcell_base):
"""
A single bit cell (6T, 8T, etc.) This module implements the
single memory cell used in the design. It is a hand-made cell, so
the layout and netlist should be available in the technology
library.
"""
def __init__(self, version="opt1", name=""):
if version == "opt1":
cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1"
elif version == "opt1a":
cell_name = "sky130_fd_bd_sram__sram_sp_cell_opt1a"
else:
debug.error("Invalid sky130 cell name", -1)
super().__init__(name, cell_name=cell_name, prop=props.bitcell_1port)
debug.info(2, "Create bitcell")
def build_graph(self, graph, inst_name, port_nets):
"""
Adds edges based on inputs/outputs.
Overrides base class function.
"""
self.add_graph_edges(graph, port_nets)

View File

@ -0,0 +1,93 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
from bitcell_array import bitcell_array
from sky130_bitcell_base_array import sky130_bitcell_base_array
from globals import OPTS
from sram_factory import factory
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()
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.add_mod(self.cell)
self.cell2 = factory.create(module_type=OPTS.bitcell, version="opt1a")
self.add_mod(self.cell2)
self.strap = factory.create(module_type="internal", version="wlstrap")
self.add_mod(self.strap)
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.add_mod(self.strap2)
self.strap3 = factory.create(module_type="internal", version="wlstrapa")
self.add_mod(self.strap3)
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):
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.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:
row_layout.append(self.strap2)
self.add_inst(name="row_{}_col_{}_wlstrap".format(row, col),
mod=self.strap2)
alternate_strap = 0
else:
if row % 2:
row_layout.append(self.strap3)
self.add_inst(name="row_{}_col_{}_wlstrap".format(row, col),
mod=self.strap3)
else:
row_layout.append(self.strap)
self.add_inst(name="row_{}_col_{}_wlstrap".format(row, col),
mod=self.strap)
alternate_strap = 1
self.connect_inst(self.get_strap_pins(row, col))
if alternate_bitcell == 0:
alternate_bitcell = 1
else:
alternate_bitcell = 0
self.array_layout.append(row_layout)

View File

@ -0,0 +1,147 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import geometry
from sram_factory import factory
from bitcell_base_array import bitcell_base_array
from globals import OPTS
from tech import layer
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)
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):
"""
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("gnd") # gnd
bitcell_pins.append("vdd") # vdd
bitcell_pins.append("vdd") # vpb
bitcell_pins.append("gnd") # vnb
bitcell_pins.extend([x for x in self.all_wordline_names if x.endswith("_{0}".format(row))])
return bitcell_pins
def get_strap_pins(self, row, col):
"""
Creates a list of connections in the strap cell,
indexed by column and row, for instance use in bitcell_array
"""
strap_pins = ["vdd"]
return strap_pins
def get_col_cap_pins(self, row, col):
"""
"""
strap_pins = ["gnd", "gnd", "vdd"]
return strap_pins
def get_col_cap_p_pins(self, row, col):
"""
"""
strap_pins = []
for port in self.all_ports:
strap_pins.extend([x for x in self.get_bitline_names(port) if "bl" in x and x.endswith("_{0}".format(col))])
strap_pins.extend(["vdd", "gnd"])
for port in self.all_ports:
strap_pins.extend([x for x in self.get_bitline_names(port) if "br" in x and x.endswith("_{0}".format(col))])
return strap_pins
def get_row_cap_pins(self, row, col):
"""
"""
strap_pins = ["gnd", "vdd", "gnd"]
return strap_pins
def get_corner_pins(self):
"""
"""
strap_pins = ["vdd", "gnd", "vdd"]
return strap_pins
def add_supply_pins(self):
""" Add the layout pins """
# 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)
if row == 2: #add only 1 label per col
if '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' in self.cell_inst[row, col].mod.pins:
try:
from 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())

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import design
from tech import cell_properties as props
class sky130_col_cap(design.design):
def __init__(self, version, name=""):
if version == "colend":
cell_name = "sky130_fd_bd_sram__sram_sp_colend"
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
elif version == "colend_cent":
cell_name = "sky130_fd_bd_sram__sram_sp_colend_cent"
prop = props.col_cap_1port_strap_power
elif version == "colenda_cent":
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)
super().__init__(name=name, cell_name=cell_name, prop=prop)

View File

@ -0,0 +1,185 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
from sram_factory import factory
from sky130_bitcell_base_array import sky130_bitcell_base_array
from globals import OPTS
class sky130_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_boundary()
self.DRC_LVS()
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.add_mod(self.colend1)
self.colend2 = factory.create(module_type="col_cap", version="colend_p_cent")
self.add_mod(self.colend2)
self.colend3 = factory.create(module_type="col_cap", version="colend_cent")
self.add_mod(self.colend3)
elif self.location == "bottom":
self.colend1 = factory.create(module_type="col_cap", version="colenda")
self.add_mod(self.colend1)
self.colend2 = factory.create(module_type="col_cap", version="colenda_p_cent")
self.add_mod(self.colend2)
self.colend3 = factory.create(module_type="col_cap", version="colenda_cent")
self.add_mod(self.colend3)
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("vdd")
pins.append("gnd")
pins.append("fake_br_{}".format(bitline))
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("vdd")
pins.append("gnd")
pins.append("fake_br_{}".format(bitline))
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("gnd")
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")
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":
self.add_layout_pin_rect_center(text="vdd",
layer=pin.layer,
offset=inst.lr(),
width=pin.width(),
height=pin.height())
elif pin_name == "gnd":
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":
self.add_layout_pin_rect_center(text="vdd",
layer=pin.layer,
offset=inst.ll(),
width=pin.width(),
height=pin.height())
elif pin_name == "gnd":
self.add_layout_pin_rect_center(text="gnd",
layer=pin.layer,
offset=inst.lr(),
width=pin.width(),
height=pin.height())
return
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]

View File

@ -0,0 +1,33 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import design
import utils
from tech import layer, GDS
class sky130_corner(design.design):
def __init__(self, location, name=""):
super().__init__(name)
if location == "ul":
self.name = "sky130_fd_bd_sram__sram_sp_corner"
elif location == "ur":
self.name = "sky130_fd_bd_sram__sram_sp_cornerb"
elif location == "ll":
self.name = "sky130_fd_bd_sram__sram_sp_cornera"
elif location == "lr":
self.name = "sky130_fd_bd_sram__sram_sp_cornera"
else:
debug.error("Invalid sky130_corner location", -1)
design.design.__init__(self, name=self.name)
(self.width, self.height) = utils.get_libcell_size(self.name,
GDS["unit"],
layer["mem"])
# pin_map = utils.get_libcell_pins(pin_names, self.name, GDS["unit"])

View File

@ -0,0 +1,153 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
from sky130_bitcell_base_array import sky130_bitcell_base_array
from sram_factory import factory
from globals import OPTS
class sky130_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_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.add_mod(self.dummy_cell)
self.dummy_cell2 = factory.create(module_type=OPTS.dummy_bitcell, version="opt1a")
self.add_mod(self.dummy_cell2)
self.strap = factory.create(module_type="internal", version="wlstrap")
self.add_mod(self.strap)
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.add_mod(self.strap2)
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):
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:
row_layout.append(self.strap2)
self.add_inst(name="row_{}_col_{}_wlstrap".format(row, col),
mod=self.strap2)
alternate_strap = 0
else:
row_layout.append(self.strap)
self.add_inst(name="row_{}_col_{}_wlstrap".format(row, col),
mod=self.strap)
alternate_strap = 1
self.connect_inst(self.get_strap_pins(row, col))
if alternate_bitcell == 0:
alternate_bitcell = 1
else:
alternate_bitcell = 0
self.array_layout.append(row_layout)
def add_pins(self):
# bitline pins are not added because they are floating
for wl_name in self.get_wordline_names():
self.add_pin(wl_name, "INPUT")
for bl in range(self.column_size):
self.add_pin("dummy_bl_{}".format(bl))
self.add_pin("dummy_br_{}".format(bl))
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
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])
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)
# 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 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()

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
from tech import cell_properties as props
import bitcell_base
class sky130_dummy_bitcell(bitcell_base.bitcell_base):
"""
A single bit cell (6T, 8T, etc.) This module implements the
single memory cell used in the design. It is a hand-made cell, so
the layout and netlist should be available in the technology
library.
"""
def __init__(self, version, name=""):
# Ignore the name argument
if version == "opt1":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_dummy"
elif version == "opt1a":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_dummy"
super().__init__(name, cell_name, prop=props.bitcell_1port)
debug.info(2, "Create dummy bitcell")

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import design
import utils
from tech import layer, GDS
class sky130_internal(design.design):
def __init__(self, version, name=""):
super().__init__(name)
if version == "wlstrap":
self.name = "sky130_fd_bd_sram__sram_sp_wlstrap"
elif version == "wlstrap_p":
self.name = "sky130_fd_bd_sram__sram_sp_wlstrap_p"
elif version == "wlstrapa":
self.name = "sky130_fd_bd_sram__sram_sp_wlstrapa"
else:
debug.error("Invalid version", -1)
design.design.__init__(self, name=self.name)
(self.width, self.height) = utils.get_libcell_size(self.name,
GDS["unit"],
layer["mem"])
# pin_map = utils.get_libcell_pins(pin_names, self.name, GDS["unit"])

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import bitcell_base
from tech import parameter, drc
from tech import cell_properties as props
import logical_effort
class sky130_replica_bitcell(bitcell_base.bitcell_base):
"""
A single bit cell (6T, 8T, etc.)
This module implements the single memory cell used in the design. It
is a hand-made cell, so the layout and netlist should be available in
the technology library. """
def __init__(self, version, name=""):
if version == "opt1":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1_replica"
elif version == "opt1a":
cell_name = "sky130_fd_bd_sram__openram_sp_cell_opt1a_replica"
super().__init__(name, cell_name, prop=props.bitcell_1port)
debug.info(2, "Create replica bitcell object")
def get_stage_effort(self, load):
parasitic_delay = 1
size = 0.5 # This accounts for bitline being drained thought the access TX and internal node
cin = 3 # Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
read_port_load = 0.5 # min size NMOS gate load
return logical_effort.logical_effort('bitline', size, cin, load + read_port_load, parasitic_delay, False)
def input_load(self):
"""Return the relative capacitance of the access transistor gates"""
# FIXME: This applies to bitline capacitances as well.
access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"]
return 2 * access_tx_cin
def analytical_power(self, corner, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 # temporary
total_power = self.return_power(dynamic, leakage)
return total_power
def build_graph(self, graph, inst_name, port_nets):
"""Adds edges based on inputs/outputs. Overrides base class function."""
self.add_graph_edges(graph, port_nets)

View File

@ -0,0 +1,340 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
from replica_bitcell_array import replica_bitcell_array
from vector import vector
from sky130_bitcell_base_array import sky130_bitcell_base_array
from utils import round_to_grid
from math import sqrt
from tech import drc
from tech import array_row_multiple
from tech import array_col_multiple
from globals import OPTS
class sky130_replica_bitcell_array(replica_bitcell_array, sky130_bitcell_base_array):
"""
Creates a bitcell arrow 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=""):
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
self.all_ports = list(range(total_ports))
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()
self.add_pin("vpb", "BIAS")
self.add_pin("vnb", "BIAS")
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))
#self.add_power_pin(name=pin_name,
# loc=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0),
# start_layer=pin.layer,
# end_layer='m2')
# 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))
#self.add_power_pin(name=pin_name,
# loc=pin_center+supply_inst.ll()+cell_inst.ll() + vector(connection_offset,0),
# start_layer=pin.layer)
min_area = drc["minarea_{}".format('m3')]
for track,supply, offset in zip(range(1,5),['vdd','vpb','vnb','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
for (bl_name, pin_name) in zip(names, pin_names):
pin = inst.get_pin(pin_name)
if 'rbl_bl' in bl_name:
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:
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

@ -0,0 +1,261 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
from sky130_bitcell_base_array import sky130_bitcell_base_array
from sram_factory import factory
from globals import OPTS
import geometry
from tech import layer
class sky130_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.
rbl is a tuple with the number of left and right replica bitlines.
Replica bit specifies which replica column this is (to determine where to put the
replica cell relative to the bottom (including the dummy bit at 0).
"""
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.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")
def add_modules(self):
self.replica_cell = factory.create(module_type="replica_bitcell_1port", version="opt1")
self.add_mod(self.replica_cell)
self.cell = self.replica_cell
self.replica_cell2 = factory.create(module_type="replica_bitcell_1port", version="opt1a")
self.add_mod(self.replica_cell2)
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.add_mod(self.strap1)
self.strap2 = factory.create(module_type="internal", version="wlstrap_p")
self.add_mod(self.strap2)
self.colend = factory.create(module_type="col_cap", version="colend")
self.edge_cell = self.colend
self.add_mod(self.colend)
self.colenda = factory.create(module_type="col_cap", version="colenda")
self.add_mod(self.colenda)
self.colend_p_cent = factory.create(module_type="col_cap", version="colend_p_cent")
self.add_mod(self.colend_p_cent)
self.colenda_p_cent = factory.create(module_type="col_cap", version="colenda_p_cent")
self.add_mod(self.colenda_p_cent)
def create_instances(self):
self.cell_inst = {}
self.array_layout = []
alternate_bitcell = (self.rows + 1) % 2
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", mod=self.strap2)
self.connect_inst(self.get_strap_pins(row, 0))
alternate_bitcell = 1
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.strap2)
self.add_inst(name=name + "_strap", mod=self.strap2)
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_p_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_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_p_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_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
else:
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 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),
layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1),
width=self.width,
height=wl_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' 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' in self.cell_inst[row].mod.pins:
try:
from 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 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

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import debug
import design
from tech import cell_properties as props
class sky130_row_cap(design.design):
def __init__(self, version, name=""):
if version == "rowend":
cell_name = "sky130_fd_bd_sram__sram_sp_rowend"
elif version == "rowenda":
cell_name = "sky130_fd_bd_sram__sram_sp_rowenda"
elif version == "rowend_replica":
cell_name = "sky130_fd_bd_sram__openram_sp_rowend_replica"
elif version == "rowenda_replica":
cell_name = "sky130_fd_bd_sram__openram_sp_rowenda_replica"
else:
debug.error("Invalid type for row_end", -1)
super().__init__(name=name, cell_name=cell_name, prop=props.row_cap_1port_cell)

View File

@ -0,0 +1,152 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
from sram_factory import factory
from sky130_bitcell_base_array import sky130_bitcell_base_array
from globals import OPTS
class sky130_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
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()
def add_modules(self):
""" Add the modules used in this design """
if self.column_offset == 0:
self.top_corner = factory.create(module_type="corner", location="ul")
self.add_mod(self.top_corner)
self.bottom_corner =factory.create(module_type="corner", location="ll")
self.add_mod(self.bottom_corner)
self.rowend1 = factory.create(module_type="row_cap", version="rowend_replica")
self.add_mod(self.rowend1)
self.rowend2 = factory.create(module_type="row_cap", version="rowenda_replica")
self.add_mod(self.rowend2)
else:
self.top_corner = factory.create(module_type="corner", location="ur")
self.add_mod(self.top_corner)
self.bottom_corner = factory.create(module_type="corner", location="lr")
self.add_mod(self.bottom_corner)
self.rowend1 = factory.create(module_type="row_cap", version="rowend")
self.add_mod(self.rowend1)
self.rowend2 = factory.create(module_type="row_cap", version="rowenda")
self.add_mod(self.rowend2)
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.
if (row < self.rows + 1 and row > 0):
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
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")
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())
# 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)

View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
"""
Import tech specific modules.
"""
from .tech import *

View File

@ -0,0 +1,790 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California
# All rights reserved.
#
import os
from design_rules import *
from module_type import *
from custom_cell_properties import cell_properties, cell
from custom_layer_properties import layer_properties
"""
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 = 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["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 = 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_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.col_cap_1port_bitcell = cell(['br', 'vdd', 'gnd', 'bl'],
['INPUT', 'INPUT', 'GROUND', 'POWER'],
{'bl': 'BL0',
'br': 'BL1',
'vdd': 'VPWR',
'gnd': 'VGND'})
cell_properties.col_cap_1port_bitcell.boundary_layer = "mem"
cell_properties.col_cap_1port_strap_power = 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 = 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 = 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 = 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")
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"]}
layer_override_name = {"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 = 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
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"]]}
# 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["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"
# Calibre automatically scales to micron to SI units and requires mult parameter
lvs_lib = "calibre_lvs_lib"
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
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"]