Switched to GF180D for extra metal layers, Fixed drc parameters so contacts are valid. ptx.py modified to achieve proper layer placement with gf180. ROM array and precharge DRC clean.

This commit is contained in:
Sage Walker 2023-07-25 14:45:14 -07:00 committed by SWalker
parent 0040efb86f
commit d6cb15c82d
11 changed files with 129 additions and 51 deletions

View File

@ -10,7 +10,7 @@ from openram.tech import drc, layer, preferred_directions
from openram.tech import layer as tech_layers
from .hierarchy_design import hierarchy_design
from .vector import vector
from .utils import ceil
class contact(hierarchy_design):
"""
@ -125,6 +125,8 @@ class contact(hierarchy_design):
self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name))
self.first_layer_minarea = drc("minarea_{0}".format(self.first_layer_name))
# If there's a different rule for active
# FIXME: Make this more elegant
if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys():
@ -135,7 +137,7 @@ class contact(hierarchy_design):
self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name))
self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
self.second_layer_minarea = drc("minarea_{0}".format(self.second_layer_name))
# In some technologies, the minimum width may be larger
# than the overlap requirement around the via, so
# check this for each dimension.
@ -143,7 +145,7 @@ class contact(hierarchy_design):
self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure,
(self.first_layer_minwidth - self.contact_array_width) / 2)
self.first_layer_vertical_enclosure = max(self.first_layer_extend,
(self.first_layer_minwidth - self.contact_array_height) / 2)
(self.first_layer_minwidth - self.contact_array_height) / 2)
elif self.directions[0] == "H":
self.first_layer_horizontal_enclosure = max(self.first_layer_extend,
(self.first_layer_minwidth - self.contact_array_width) / 2)
@ -166,7 +168,7 @@ class contact(hierarchy_design):
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
(self.second_layer_minwidth - self.contact_array_width) / 2)
else:
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
debug.error("Invalid second layer direction: ".format(self.directions[1]), -1)
def create_contact_array(self):
""" Create the contact array at the origin"""
@ -221,6 +223,18 @@ class contact(hierarchy_design):
first_layer_name = "tap"
else:
first_layer_name = self.first_layer_name
area = self.first_layer_width * self.first_layer_height
if area < self.first_layer_minarea and self.is_well_contact:
if self.directions[0] == "V":
area_extend = (self.first_layer_minarea / self.first_layer_width) - self.first_layer_height
self.first_layer_height = ceil(self.first_layer_height + area_extend)
self.first_layer_position = self.first_layer_position - vector(0, area_extend / 2)
elif self.directions[0] == "H":
area_extend = (self.first_layer_minarea / self.first_layer_height) - self.first_layer_width
self.first_layer_width = ceil(self.first_layer_height + area_extend)
self.first_layer_position = self.first_layer_position - vector(area_extend / 2, 0)
self.add_rect(layer=first_layer_name,
offset=self.first_layer_position,
width=self.first_layer_width,
@ -236,6 +250,7 @@ class contact(hierarchy_design):
self.second_layer_minwidth)
self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure,
self.second_layer_minwidth)
self.add_rect(layer=self.second_layer_name,
offset=self.second_layer_position,
width=self.second_layer_width,

View File

@ -22,7 +22,7 @@ from openram.sram_factory import factory
from openram import OPTS
from .vector import vector
from .pin_layout import pin_layout
from .utils import round_to_grid
from .utils import round_to_grid, ceil
from . import geometry
try:
@ -838,8 +838,7 @@ class layout():
from_id = tech_layer_indices[from_layer]
to_id = tech_layer_indices[to_layer]
layer_list = [x for x in tech_layer_indices.keys() if tech_layer_indices[x] >= from_id and tech_layer_indices[x] < to_id]
layer_list = [x for x in tech_layer_indices.keys() if tech_layer_indices[x] > from_id and tech_layer_indices[x] < to_id]
return layer_list
@ -1368,12 +1367,11 @@ class layout():
min_width = drc("minwidth_{}".format(layer))
if preferred_directions[layer] == "V":
new_height = max(min_area / width, min_width)
new_height = ceil(max(min_area / width, min_width))
new_width = width
else:
new_width = max(min_area / height, min_width)
new_width = ceil(max(min_area / height, min_width))
new_height = height
debug.check(min_area <= round_to_grid(new_height*new_width), "Min area violated.")
self.add_rect_center(layer=layer,

View File

@ -129,12 +129,15 @@ class ptx(design):
# be decided in the layout later.
area_sd = 2.5 * self.poly_width * self.tx_width
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
# self.channel_length = drc("minlength_channel") if OPTS.tech_name != "gf180mcu" else drc("minlength_channel_" + self.tx_type)
self.channel_length = drc("minlength_channel")
if cell_props.ptx.model_is_subckt:
# sky130
main_str = "X{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.channel_length)
# Perimeters are in microns
# Area is in u since it is microns square
area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd,
@ -143,7 +146,7 @@ class ptx(design):
main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.channel_length)
area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd,
area_sd)
self.spice_device = main_str + area_str
@ -160,17 +163,17 @@ class ptx(design):
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format("nshort" if self.tx_type == "nmos" else "pshort",
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.channel_length)
elif cell_props.ptx.model_is_subckt:
self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2}u l={3}u".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.channel_length)
else:
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc("minwidth_poly"))
self.channel_length)
def setup_layout_constants(self):
"""
@ -196,6 +199,9 @@ class ptx(design):
directions=("V", "V"),
dimensions=(1, self.num_contacts))
if OPTS.tech_name == "gf180mcu":
self.poly_width = self.channel_length
# This is the extra poly spacing due to the poly contact to poly contact pitch
# of contacted gates
extra_poly_contact_width = self.poly_contact.width - self.poly_width
@ -216,11 +222,12 @@ class ptx(design):
self.active_width = 2 * self.end_to_contact + self.active_contact.width \
+ 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch
# Active height is just the transistor width
self.active_height = self.tx_width
# Active height is either the transistor width or the wide enough to enclose the active contact
self.active_height = max(self.tx_width, self.active_contact.width + 2 * self.active_enclose_contact)
# Poly height must include poly extension over active
self.poly_height = self.tx_width + 2 * self.poly_extend_active
self.poly_height = self.active_height + 2 * self.poly_extend_active
self.active_offset = vector([self.well_enclose_active] * 2)

View File

@ -10,7 +10,7 @@
import math
from .bitcell_base_array import bitcell_base_array
from openram.base import vector
from openram import OPTS, debug
from openram import debug
from openram.sram_factory import factory
from openram.tech import drc, layer
@ -156,7 +156,7 @@ class rom_base_array(bitcell_base_array):
name = "bit_r{0}_c{1}".format(row, col)
# when col = 0, bl_h is connected to precharge, otherwise connect to previous bl connection
# when col = col_size - 1 connected column_sizeto gnd otherwise create new bl connection
# when col = col_size - 1 connected to gnd otherwise create new bl connection
# debug.info(1, "Create cell: r{0}, c{1}".format(row, col))
if row == self.row_size:

View File

@ -80,6 +80,8 @@ class rom_base_cell(design):
self.cell_inst = self.add_inst( name=self.name + "_nmos",
mod=self.nmos,
)
print("bitmos", self.cell_inst.height, self.cell_inst.width)
if self.bit_value == 0:
self.connect_inst(["bl", "wl", "bl", "gnd"])
else:
@ -103,6 +105,8 @@ class rom_base_cell(design):
self.copy_layout_pin(self.cell_inst, "S", "S")
self.copy_layout_pin(self.cell_inst, "D", "D")
self.copy_layout_pin(self.cell_inst, "G", "G")
self.source_pos = self.cell_inst.get_pin("S").center()
def place_poly(self):

View File

@ -36,7 +36,7 @@ class rom_poly_tap(design):
self.place_via()
self.add_boundary()
self.extend_poly()
# self.extend_poly()
if self.add_tap or self.place_poly:
self.place_active_tap()

View File

@ -17,9 +17,10 @@ class rom_precharge_array(design):
"""
An array of inverters to create the inverted address lines for the rom decoder
"""
def __init__(self, cols, name="", bitline_layer=None, strap_spacing=None, strap_layer="m2", tap_direction="row"):
def __init__(self, cols, name="", bitline_layer="m2", strap_spacing=None, strap_layer="m3", tap_direction="row"):
self.cols = cols
self.strap_layer = strap_layer
self.bitline_layer = bitline_layer
self.tap_direction = tap_direction
if "li" in layer:
@ -27,14 +28,6 @@ class rom_precharge_array(design):
else:
self.supply_layer = "m1"
if bitline_layer is not None:
self.bitline_layer = bitline_layer
else:
self.bitline_layer = self.supply_layer
if name=="":
name = "rom_inv_array_{0}".format(cols)
if strap_spacing != None:
self.strap_spacing = strap_spacing
@ -128,7 +121,7 @@ class rom_precharge_array(design):
for col in range(self.cols):
if col % self.strap_spacing == 0:
self.tap_insts[strap_num].place(vector(cell_x, cell_y + self.poly_tap.height))
self.tap_insts[strap_num].place(vector(cell_x + self.poly_space, cell_y + self.poly_tap.height))
strap_num += 1
if self.tap_direction == "col":
@ -137,7 +130,7 @@ class rom_precharge_array(design):
self.pmos_insts[col].place(vector(cell_x, cell_y))
cell_x += self.pmos.width
self.tap_insts[strap_num].place(vector(cell_x, cell_y + self.poly_tap.height))
self.tap_insts[strap_num].place(vector(cell_x + self.poly_space, cell_y + self.poly_tap.height))
def create_layout_pins(self):
self.copy_layout_pin(self.tap_insts[0], "poly_tap", "gate")
@ -158,11 +151,12 @@ class rom_precharge_array(design):
for tap in self.tap_insts:
tap_pin = tap.get_pin("poly_tap")
start = vector(tap_pin.cx(), tap_pin.by())
end = vector(start.x, tap.mod.get_pin("poly_tap").cy())
end = vector(start.x, self.pmos_insts[0].get_pin("G").cy())
self.add_segment_center(layer="poly", start=start, end=end)
offset_start = vector(end.x - self.poly_tap.width + self.poly_extend_active, end.y)
offset_end = end + vector(0.5*self.poly_width, 0)
self.add_segment_center(layer="poly", start=offset_start, end=offset_end)
self.add_segment_center(layer="poly", start=self.pmos_insts[-1].get_pin("G").center(), end=offset_end)
def extend_well(self):
self.well_offset = self.pmos.tap_offset

View File

@ -24,6 +24,8 @@ class rom_precharge_cell(rom_base_cell):
self.place_tap()
self.extend_well()
print("precharge", self.height, self.width)
def add_modules(self):
if OPTS.tech_name == "sky130":
@ -42,6 +44,7 @@ class rom_precharge_cell(rom_base_cell):
self.cell_inst = self.add_inst( name="precharge_pmos",
mod=self.pmos,
)
print("premos", self.cell_inst.height, self.cell_inst.width)
self.connect_inst(["bitline", "gate", "vdd", "vdd"])
def add_pins(self):
@ -55,10 +58,10 @@ class rom_precharge_cell(rom_base_cell):
self.poly_size = (self.cell_inst.width + self.active_space) - (self.cell_inst.height + 2 * self.poly_extend_active)
def extend_well(self):
well_y = self.get_pin("vdd").cy() - 0.5 * self.nwell_width
print(self.nwell_enclose_active)
well_y = self.get_pin("vdd").cy() - 0.5 * self.tap.height - self.nwell_enclose_active
well_ll = vector(0, well_y)
height = self.get_pin("D").cy() + 0.5 * self.nwell_width - well_y
height = self.get_pin("D").cy() + self.nwell_enclose_active - well_y
self.add_rect("nwell", well_ll, self.width , height)
def place_tap(self):
@ -67,7 +70,7 @@ class rom_precharge_cell(rom_base_cell):
self.tap_offset = abs(tap_y)
pos = vector(source.cx(), tap_y )
self.add_via_center(layers=self.active_stack,
self.tap = self.add_via_center(layers=self.active_stack,
offset=pos,
implant_type="n",
well_type="n",

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import sys, os
import unittest
from testutils import *
import openram
from openram import debug
from openram.sram_factory import factory
from openram import OPTS
class precharge_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
openram.init_openram(config_file, is_unit_test=True)
# check precharge in single port
debug.info(2, "Testing rom precharge bitcell")
tx = factory.create(module_type="rom_precharge_cell", module_name="precharge_cell", bitline_layer="m2", supply_layer="m1")
self.local_check(tx)
openram.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = openram.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -22,7 +22,7 @@ os.environ["MGC_TMPDIR"] = "/tmp"
# 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'], 'gf180mcuA', 'libs.tech')
open_pdks = os.path.join(os.environ['PDK_ROOT'], 'gf180mcuD', 'libs.tech')
else:
raise SystemError("Unable to find open_pdks tech file. Set PDK_ROOT.")
@ -34,7 +34,7 @@ if not os.path.exists(gf180_lib_ngspice):
os.environ["SPICE_MODEL_DIR"] = spice_model_dir
open_pdks = os.path.abspath(open_pdks)
gf180_magicrc = os.path.join(open_pdks, 'magic', "gf180mcuA.magicrc")
gf180_magicrc = os.path.join(open_pdks, 'magic', "gf180mcuD.magicrc")
if not os.path.exists(gf180_magicrc):
raise SystemError("Did not find {} under {}".format(gf180_magicrc, open_pdks))
os.environ["OPENRAM_MAGICRC"] = gf180_magicrc

View File

@ -31,7 +31,7 @@ tech_modules["bitcell_1port"] = "gf180_bitcell"
cell_properties = d.cell_properties()
# is there a better way to tell if the user overrode the port order than this?
# this is needed to correctly create the bitcell_pins list in the bitcell_base_array
# this is needed to correctly create the bitcell_pins list in the bitcell_base_array
cell_properties.override_bitcell_1port_order = True
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"]
@ -189,8 +189,13 @@ drc = d.design_rules("gf180")
# grid size
drc["grid"] = 0.005
drc["minwidth_tx"] = 0.28
#drc["minlength_channel"] = 0.150
# minwidth_tx with contact (no dog bone transistors)
drc["minwidth_tx"] = 0.5
# PL.2 Min gate width/channel length for 6V pmos (0.7 for 6V nmos)
drc["minlength_channel"] = 0.7
drc["minlength_channel_pmos"] = 0.55
drc["minlength_channel_nmos"] = 0.7
drc["pwell_to_nwell"] = 0 # assuming same potential
@ -199,11 +204,13 @@ drc.add_layer("nwell",
spacing=0.6)
drc.add_layer("pwell",
width=0.74, # 0.6 for 1.5v
spacing=0.86) # equal potential 1.7 otherwise
width=0.74, # 0.6 for 3.3v
spacing=0.86) # equal potential
# PL.1 minwidth of interconnect poly 5/6V
# PL.3a poly spacing 5/6V
drc.add_layer("poly",
width=0.18,
width=0.2,
spacing=0.24)
drc["poly_extend_active"] = 0.22
@ -216,9 +223,14 @@ drc["poly_to_active"] = 0.1
#drc["poly_to_field_poly"] = 0.210
#
# DF.1a - minwidth of active (5/6V)
# DF.3a - minspacing of active of the same type (5/6V)
# DF.9 - minarea of active area=0.2025 (5/6V)
drc.add_layer("active",
width=0.22,
spacing=0.280)
width=0.3,
spacing=0.36,
area=0.2025)
drc.add_enclosure("dnwell",
layer="pwell",
@ -250,22 +262,27 @@ drc.add_layer("implant",
drc.add_layer("contact",
width=0.22,
spacing=0.25)
# CO.4 - active enclosure of contact
# extension is not a true drc rule, used to extend active to reach active min area
drc.add_enclosure("active",
layer="contact",
enclosure=0.01,
extension=0.01)
enclosure=0.07,
extension=0.07)
drc.add_enclosure("poly",
layer="contact",
enclosure=0.07,
extension=0.07)
drc["active_contact_to_gate"] = 0.145
drc["active_contact_to_gate"] = 0.15
drc["poly_contact_to_gate"] = 0.165
#drc["npc_enclose_poly"] = 0.1
# M1.1 - width
# M1.2a - space
# M1.3 - area
drc.add_layer("m1",
width=0.23,
spacing=0.23,