From d6cb15c82ddabe111da1562d6b24ac87b18ce0e1 Mon Sep 17 00:00:00 2001 From: Sage Walker Date: Tue, 25 Jul 2023 14:45:14 -0700 Subject: [PATCH] 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. --- compiler/base/contact.py | 23 +++++++++++--- compiler/base/hierarchy_layout.py | 10 +++--- compiler/modules/ptx.py | 23 +++++++++----- compiler/modules/rom_base_array.py | 4 +-- compiler/modules/rom_base_cell.py | 4 +++ compiler/modules/rom_poly_tap.py | 2 +- compiler/modules/rom_precharge_array.py | 18 ++++------- compiler/modules/rom_precharge_cell.py | 11 ++++--- compiler/tests/04_rom_precharge_test.py | 40 ++++++++++++++++++++++++ technology/gf180mcu/__init__.py | 4 +-- technology/gf180mcu/tech/tech.py | 41 +++++++++++++++++-------- 11 files changed, 129 insertions(+), 51 deletions(-) create mode 100644 compiler/tests/04_rom_precharge_test.py diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 84f60d0e..ea7280a1 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -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, diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 7137566f..504cf6a9 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -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, diff --git a/compiler/modules/ptx.py b/compiler/modules/ptx.py index 250cd3f6..995bef1f 100644 --- a/compiler/modules/ptx.py +++ b/compiler/modules/ptx.py @@ -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) diff --git a/compiler/modules/rom_base_array.py b/compiler/modules/rom_base_array.py index 6907d588..804ce70a 100644 --- a/compiler/modules/rom_base_array.py +++ b/compiler/modules/rom_base_array.py @@ -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: diff --git a/compiler/modules/rom_base_cell.py b/compiler/modules/rom_base_cell.py index 2c8bf669..1f47b88f 100644 --- a/compiler/modules/rom_base_cell.py +++ b/compiler/modules/rom_base_cell.py @@ -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): diff --git a/compiler/modules/rom_poly_tap.py b/compiler/modules/rom_poly_tap.py index a9aaeb99..2bbbe18f 100644 --- a/compiler/modules/rom_poly_tap.py +++ b/compiler/modules/rom_poly_tap.py @@ -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() diff --git a/compiler/modules/rom_precharge_array.py b/compiler/modules/rom_precharge_array.py index 819f14ed..995f2ef9 100644 --- a/compiler/modules/rom_precharge_array.py +++ b/compiler/modules/rom_precharge_array.py @@ -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 diff --git a/compiler/modules/rom_precharge_cell.py b/compiler/modules/rom_precharge_cell.py index 1777e507..6a2f4ac7 100644 --- a/compiler/modules/rom_precharge_cell.py +++ b/compiler/modules/rom_precharge_cell.py @@ -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", diff --git a/compiler/tests/04_rom_precharge_test.py b/compiler/tests/04_rom_precharge_test.py new file mode 100644 index 00000000..aa0c443b --- /dev/null +++ b/compiler/tests/04_rom_precharge_test.py @@ -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()) diff --git a/technology/gf180mcu/__init__.py b/technology/gf180mcu/__init__.py index 10a3899a..48aedd88 100644 --- a/technology/gf180mcu/__init__.py +++ b/technology/gf180mcu/__init__.py @@ -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 diff --git a/technology/gf180mcu/tech/tech.py b/technology/gf180mcu/tech/tech.py index cce0f828..4d1ed4e8 100644 --- a/technology/gf180mcu/tech/tech.py +++ b/technology/gf180mcu/tech/tech.py @@ -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,