Thin-cell decoder changes.

Add hard decoder gates (nand, inv, pnan)
Add conditions for routing using LI layer in s8.
Generalize bus layers for decoders.
Move custom cells to own directory.
Fixed via directions, etc.
Add 4x16 hierarchical decoder and test case
This commit is contained in:
mrg
2020-05-29 10:36:07 -07:00
parent a305d788d7
commit 4a67f7dc71
49 changed files with 1991 additions and 552 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ from sram_factory import factory
class pand2(pgate.pgate):
"""
This is a simple buffer used for driving loads.
This is an AND (or NAND) with configurable drive strength.
"""
def __init__(self, name, size=1, height=None, vertical=False, add_wells=True):
debug.info(1, "Creating pand2 {}".format(name))
+6 -6
View File
@@ -17,13 +17,13 @@ class pdriver(pgate.pgate):
sized for driving a load.
"""
def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None, add_wells=True):
def __init__(self, name, inverting=False, fanout=0, size_list=None, height=None, add_wells=True):
debug.info(1, "creating pdriver {}".format(name))
self.stage_effort = 3
self.height = height
self.neg_polarity = neg_polarity
self.inverting = inverting
self.size_list = size_list
self.fanout = fanout
@@ -31,8 +31,8 @@ class pdriver(pgate.pgate):
debug.error("Either fanout or size list must be specified.", -1)
if self.size_list and self.fanout != 0:
debug.error("Cannot specify both size_list and fanout.", -1)
if self.size_list and self.neg_polarity:
debug.error("Cannot specify both size_list and neg_polarity.", -1)
if self.size_list and self.inverting:
debug.error("Cannot specify both size_list and inverting.", -1)
# Creates the netlist and layout
pgate.pgate.__init__(self, name, height, add_wells)
@@ -47,9 +47,9 @@ class pdriver(pgate.pgate):
int(round(self.fanout ** (1 / self.stage_effort))))
# Increase the number of stages if we need to fix polarity
if self.neg_polarity and (self.num_stages % 2 == 0):
if self.inverting and (self.num_stages % 2 == 0):
self.num_stages += 1
elif not self.neg_polarity and (self.num_stages % 2):
elif not self.inverting and (self.num_stages % 2):
self.num_stages += 1
self.size_list = []
-1
View File
@@ -44,7 +44,6 @@ class pgate(design.design):
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
# This is the space from a S/D contact to the supply rail
# Assume the contact starts at the active edge
contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space
# This is a poly-to-poly of a flipped cell
poly_to_poly_gate_space = self.poly_extend_active + self.poly_space
+217
View File
@@ -0,0 +1,217 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 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 contact
import pinv
import debug
from tech import drc, parameter
from vector import vector
from globals import OPTS
from sram_factory import factory
if(OPTS.tech_name == "s8"):
from tech import nmos_bins, pmos_bins, accuracy_requirement
class pinv_dec(pinv.pinv):
"""
This is another version of pinv but with layout for the decoder.
Other stuff is the same (netlist, sizes, etc.)
"""
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
debug.info(2,
"creating pinv_dec structure {0} with size of {1}".format(name,
size))
if not height:
b = factory.create(module_type="bitcell")
self.cell_height = b.height
else:
self.cell_height = height
# Inputs to cells are on input layer
# Outputs from cells are on output layer
if OPTS.tech_name == "s8":
self.supply_layer = "m1"
else:
self.supply_layer = "m2"
pinv.pinv.__init__(self, name, size, beta, self.cell_height, add_wells)
def determine_tx_mults(self):
"""
Determines the number of fingers needed to achieve the size within
the height constraint. This may fail if the user has a tight height.
"""
# This is always 1 tx, because we have horizontal transistors.
self.tx_mults = 1
self.nmos_width = self.nmos_size * drc("minwidth_tx")
self.pmos_width = self.pmos_size * drc("minwidth_tx")
if OPTS.tech_name == "s8":
(self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width)
(self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width)
return
# Over-ride the route input gate to call the horizontal version.
# Other top-level netlist and layout functions are not changed.
def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None):
"""
Route the input gate to the left side of the cell for access.
Position is actually ignored and is left to be compatible with the pinv.
"""
nmos_gate_pin = nmos_inst.get_pin("G")
pmos_gate_pin = pmos_inst.get_pin("G")
# Check if the gates are aligned and give an error if they aren't!
if nmos_gate_pin.ll().y != pmos_gate_pin.ll().y:
self.gds_write("unaliged_gates.gds")
debug.check(nmos_gate_pin.ll().y == pmos_gate_pin.ll().y,
"Connecting unaligned gates not supported. See unaligned_gates.gds.")
# Pick point on the left of NMOS and up to PMOS
nmos_gate_pos = nmos_gate_pin.rc()
pmos_gate_pos = pmos_gate_pin.lc()
self.add_path("poly", [nmos_gate_pos, pmos_gate_pos])
# Center is completely symmetric.
contact_width = contact.poly_contact.width
contact_offset = nmos_gate_pin.lc() \
- vector(self.poly_extend_active + 0.5 * contact_width, 0)
via = self.add_via_stack_center(from_layer="poly",
to_layer=self.route_layer,
offset=contact_offset,
directions=directions)
self.add_path("poly", [contact_offset, nmos_gate_pin.lc()])
self.add_layout_pin_rect_center(text=name,
layer=self.route_layer,
offset=contact_offset,
width=via.mod.second_layer_width,
height=via.mod.second_layer_height)
def determine_width(self):
self.width = self.pmos_inst.rx() + self.well_extend_active
def extend_wells(self):
""" Extend bottom to top for each well. """
from tech import layer
if "pwell" in layer:
ll = self.nmos_inst.ll() - self.nmos_inst.mod.active_offset
ur = self.nmos_inst.ur() + self.nmos_inst.mod.active_offset
self.add_rect(layer="pwell",
offset=ll,
width=ur.x - ll.x,
height=self.height - ll.y)
if "nwell" in layer:
ll = self.pmos_inst.ll() - self.pmos_inst.mod.active_offset
ur = self.pmos_inst.ur() + self.pmos_inst.mod.active_offset
self.add_rect(layer="nwell",
offset=ll - vector(self.nwell_enclose_active, 0),
width=ur.x - ll.x + self.nwell_enclose_active,
height=self.height - ll.y + 2 * self.nwell_enclose_active)
def place_ptx(self):
"""
"""
# offset so that the input contact is over from the left edge by poly spacing
x_offset = self.nmos.active_offset.y + contact.poly_contact.width + self.poly_space
# center the transistor in the y-dimension
y_offset = self.nmos.width + self.active_space
self.nmos_pos = vector(x_offset, y_offset)
self.nmos_inst.place(self.nmos_pos)
self.nmos_inst.place(self.nmos_pos,
rotate=270)
# place PMOS so it is half a poly spacing down from the top
xoffset = self.nmos_inst.height + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell")
self.pmos_pos = self.nmos_pos + vector(xoffset, 0)
self.pmos_inst.place(self.pmos_pos,
rotate=270)
# Output position will be in between the PMOS and NMOS drains
pmos_drain_pos = self.pmos_inst.get_pin("D").center()
nmos_drain_pos = self.nmos_inst.get_pin("D").center()
self.output_pos = vector(0.5 * (pmos_drain_pos.x + nmos_drain_pos.x), nmos_drain_pos.y)
def route_outputs(self):
"""
Route the output (drains) together.
Optionally, routes output to edge.
"""
# Get the drain pin
nmos_drain_pin = self.nmos_inst.get_pin("D")
# Pick point at right most of NMOS and connect over to PMOS
nmos_drain_pos = nmos_drain_pin.lc()
right_side = vector(self.width, nmos_drain_pos.y)
self.add_layout_pin_segment_center("Z",
self.route_layer,
nmos_drain_pos,
right_side)
def add_well_contacts(self):
""" Add n/p well taps to the layout and connect to supplies """
source_pos = self.pmos_inst.get_pin("S").center()
contact_pos = vector(source_pos.x, self.height)
self.nwell_contact = self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="n",
well_type="n")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
source_pos = self.nmos_inst.get_pin("S").center()
contact_pos = vector(source_pos.x, self.height)
self.pwell_contact= self.add_via_center(layers=self.active_stack,
offset=contact_pos,
implant_type="p",
well_type="p")
self.add_via_stack_center(offset=contact_pos,
from_layer=self.active_stack[2],
to_layer=self.supply_layer)
def route_supply_rails(self):
pin = self.nmos_inst.get_pin("S")
source_pos = pin.center()
bottom_pos = source_pos.scale(1, 0)
top_pos = bottom_pos + vector(0, self.height)
self.add_layout_pin_segment_center("gnd",
self.supply_layer,
start=bottom_pos,
end=top_pos)
pin = self.pmos_inst.get_pin("S")
source_pos = pin.center()
bottom_pos = source_pos.scale(1, 0)
top_pos = bottom_pos + vector(0, self.height)
self.add_layout_pin_segment_center("vdd",
self.supply_layer,
start=bottom_pos,
end=top_pos)
def connect_rails(self):
""" Connect the nmos and pmos to its respective power rails """
source_pos = self.nmos_inst.get_pin("S").center()
self.add_via_stack_center(offset=source_pos,
from_layer=self.route_layer,
to_layer=self.supply_layer)
source_pos = self.pmos_inst.get_pin("S").center()
self.add_via_stack_center(offset=source_pos,
from_layer=self.route_layer,
to_layer=self.supply_layer)
+3
View File
@@ -212,7 +212,10 @@ class pnand3(pgate.pgate):
pmos_drain_bottom = self.pmos1_inst.get_pin("D").by()
self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space
# This is a more compact offset, but the bottom one works better in the decoders to "center" the pins
# in the height of the gates
self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space
# self.inputA_yoffset = self.output_yoffset - self.m1_pitch
self.route_input_gate(self.pmos1_inst,
self.nmos1_inst,
self.inputA_yoffset,
+6 -4
View File
@@ -386,10 +386,12 @@ class ptx(design.design):
well_ll = center_pos - vector(0.5 * self.well_width,
0.5 * self.well_height)
if well_name in layer:
self.add_rect(layer=well_name,
offset=well_ll,
width=self.well_width,
height=self.well_height)
well = self.add_rect(layer=well_name,
offset=well_ll,
width=self.well_width,
height=self.well_height)
setattr(self, well_name, well)
if "vtg" in layer:
self.add_rect(layer="vtg",
offset=well_ll,
+155
View File
@@ -0,0 +1,155 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 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 debug
from vector import vector
import design
from sram_factory import factory
from globals import OPTS
from tech import layer
class wordline_driver(design.design):
"""
This is an AND (or NAND) with configurable drive strength to drive the wordlines.
It is matched to the bitcell height.
"""
def __init__(self, name, size=1, height=None):
debug.info(1, "Creating wordline_driver {}".format(name))
self.add_comment("size: {}".format(size))
design.design.__init__(self, name)
if height is None:
b = factory.create(module_type="bitcell")
self.height = b.height
else:
self.height = height
self.size = size
self.create_netlist()
if not OPTS.netlist_only:
self.create_layout()
def create_netlist(self):
self.add_pins()
self.create_modules()
self.create_insts()
def create_modules(self):
if OPTS.tech_name == "s8":
self.nand = factory.create(module_type="nand2_dec")
self.height = self.nand.height
else:
self.nand = factory.create(module_type="nand2_dec",
height=self.height)
self.driver = factory.create(module_type="inv_dec",
size=self.size,
height=self.nand.height)
self.add_mod(self.nand)
self.add_mod(self.driver)
def create_layout(self):
self.width = self.nand.width + self.driver.width
if "li" in layer:
self.route_layer = "li"
else:
self.route_layer = "m1"
self.place_insts()
self.route_wires()
self.add_layout_pins()
self.route_supply_rails()
self.add_boundary()
self.DRC_LVS()
def add_pins(self):
self.add_pin("A", "INPUT")
self.add_pin("B", "INPUT")
self.add_pin("Z", "OUTPUT")
self.add_pin("vdd", "POWER")
self.add_pin("gnd", "GROUND")
def create_insts(self):
self.nand_inst = self.add_inst(name="wld_nand",
mod=self.nand)
self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"])
self.driver_inst = self.add_inst(name="wl_driver",
mod=self.driver)
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_insts(self):
# Add NAND to the right
self.nand_inst.place(offset=vector(0, 0))
# Add INV to the right
self.driver_inst.place(offset=vector(self.nand_inst.rx(), 0))
def route_supply_rails(self):
""" Add vdd/gnd rails to the top, (middle), and bottom. """
if OPTS.tech_name == "s8":
for name in ["vdd", "gnd"]:
for inst in [self.nand_inst, self.driver_inst]:
self.copy_layout_pin(inst, name)
else:
self.add_layout_pin_rect_center(text="gnd",
layer=self.route_layer,
offset=vector(0.5 * self.width, 0),
width=self.width)
y_offset = self.height
self.add_layout_pin_rect_center(text="vdd",
layer=self.route_layer,
offset=vector(0.5 * self.width, y_offset),
width=self.width)
def route_wires(self):
# nand Z to inv A
z1_pin = self.nand_inst.get_pin("Z")
a2_pin = self.driver_inst.get_pin("A")
if OPTS.tech_name == "s8":
mid1_point = vector(a2_pin.cx(), z1_pin.cy())
else:
mid1_point = vector(z1_pin.cx(), a2_pin.cy())
self.add_path(self.route_layer,
[z1_pin.center(), mid1_point, a2_pin.center()])
def add_layout_pins(self):
pin = self.driver_inst.get_pin("Z")
self.add_layout_pin_rect_center(text="Z",
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
for pin_name in ["A", "B"]:
pin = self.nand_inst.get_pin(pin_name)
self.add_layout_pin_rect_center(text=pin_name,
layer=pin.layer,
offset=pin.center(),
width=pin.width(),
height=pin.height())
def get_stage_efforts(self, external_cout, inp_is_rise=False):
"""Get the stage efforts of the A or B -> Z path"""
stage_effort_list = []
stage1_cout = self.driver.get_cin()
stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise)
stage_effort_list.append(stage1)
stage2 = self.driver.get_stage_effort(external_cout, stage1.is_rise)
stage_effort_list.append(stage2)
return stage_effort_list
def get_cin(self):
"""Return the relative input capacitance of a single input"""
return self.nand.get_cin()