mirror of https://github.com/VLSIDA/OpenRAM.git
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:
parent
0040efb86f
commit
d6cb15c82d
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue