diff --git a/.gitignore b/.gitignore index e31298d6..71c19b47 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ **/model_data outputs technology/freepdk45/ncsu_basekit +technology/sky130/*_lib +technology/sky130/tech/.magicrc .idea diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..447009f9 --- /dev/null +++ b/Makefile @@ -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) diff --git a/technology/sky130/__init__.py b/technology/sky130/__init__.py new file mode 100644 index 00000000..3df3f555 --- /dev/null +++ b/technology/sky130/__init__.py @@ -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 diff --git a/technology/sky130/modules/sky130_bitcell.py b/technology/sky130/modules/sky130_bitcell.py new file mode 100644 index 00000000..90371a29 --- /dev/null +++ b/technology/sky130/modules/sky130_bitcell.py @@ -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) diff --git a/technology/sky130/modules/sky130_bitcell_array.py b/technology/sky130/modules/sky130_bitcell_array.py new file mode 100644 index 00000000..5d250f15 --- /dev/null +++ b/technology/sky130/modules/sky130_bitcell_array.py @@ -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) diff --git a/technology/sky130/modules/sky130_bitcell_base_array.py b/technology/sky130/modules/sky130_bitcell_base_array.py new file mode 100644 index 00000000..5b0e39d4 --- /dev/null +++ b/technology/sky130/modules/sky130_bitcell_base_array.py @@ -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()) diff --git a/technology/sky130/modules/sky130_col_cap.py b/technology/sky130/modules/sky130_col_cap.py new file mode 100644 index 00000000..cd328754 --- /dev/null +++ b/technology/sky130/modules/sky130_col_cap.py @@ -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) diff --git a/technology/sky130/modules/sky130_col_cap_array.py b/technology/sky130/modules/sky130_col_cap_array.py new file mode 100644 index 00000000..b1e9e35b --- /dev/null +++ b/technology/sky130/modules/sky130_col_cap_array.py @@ -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 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 diff --git a/technology/sky130/modules/sky130_replica_column.py b/technology/sky130/modules/sky130_replica_column.py new file mode 100644 index 00000000..a900b0fe --- /dev/null +++ b/technology/sky130/modules/sky130_replica_column.py @@ -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 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) diff --git a/technology/sky130/modules/sky130_row_cap.py b/technology/sky130/modules/sky130_row_cap.py new file mode 100644 index 00000000..bdd0aa95 --- /dev/null +++ b/technology/sky130/modules/sky130_row_cap.py @@ -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) diff --git a/technology/sky130/modules/sky130_row_cap_array.py b/technology/sky130/modules/sky130_row_cap_array.py new file mode 100644 index 00000000..a82f5558 --- /dev/null +++ b/technology/sky130/modules/sky130_row_cap_array.py @@ -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 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) diff --git a/technology/sky130/tech/__init__.py b/technology/sky130/tech/__init__.py new file mode 100644 index 00000000..680b0b9c --- /dev/null +++ b/technology/sky130/tech/__init__.py @@ -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 * diff --git a/technology/sky130/tech/tech.py b/technology/sky130/tech/tech.py new file mode 100644 index 00000000..a2c4d185 --- /dev/null +++ b/technology/sky130/tech/tech.py @@ -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"]