2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2022-11-30 23:50:43 +01:00
|
|
|
# Copyright (c) 2016-2022 Regents of the University of California and The Board
|
2019-06-14 17:43:41 +02:00
|
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
|
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
|
|
|
# All rights reserved.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2020-04-10 04:39:21 +02:00
|
|
|
import math
|
|
|
|
|
from bisect import bisect_left
|
2022-11-27 22:01:20 +01:00
|
|
|
from openram import debug
|
|
|
|
|
from openram.base import design
|
|
|
|
|
from openram.base import vector
|
|
|
|
|
from openram.tech import layer, drc
|
|
|
|
|
from openram.tech import cell_properties as cell_props
|
|
|
|
|
from openram import OPTS
|
2022-07-13 19:57:56 +02:00
|
|
|
|
2020-10-28 17:54:15 +01:00
|
|
|
if cell_props.ptx.bin_spice_models:
|
2022-11-27 22:01:20 +01:00
|
|
|
from openram.tech import nmos_bins, pmos_bins
|
2019-10-06 19:30:16 +02:00
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-07-13 19:57:56 +02:00
|
|
|
class pgate(design):
|
2017-12-12 23:53:19 +01:00
|
|
|
"""
|
2019-10-06 19:30:16 +02:00
|
|
|
This is a module that implements some shared
|
|
|
|
|
functions for parameterized gates.
|
2017-12-12 23:53:19 +01:00
|
|
|
"""
|
|
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
def __init__(self, name, height=None, add_wells=True):
|
2017-12-12 23:53:19 +01:00
|
|
|
""" Creates a generic cell """
|
2020-08-12 20:15:32 +02:00
|
|
|
super().__init__(name)
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2018-09-04 20:55:22 +02:00
|
|
|
if height:
|
|
|
|
|
self.height = height
|
|
|
|
|
elif not height:
|
2020-06-05 11:53:03 +02:00
|
|
|
# By default, something simple
|
|
|
|
|
self.height = 14 * self.m1_pitch
|
|
|
|
|
self.add_wells = add_wells
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
if "li" in layer:
|
|
|
|
|
self.route_layer = "li"
|
|
|
|
|
else:
|
|
|
|
|
self.route_layer = "m1"
|
|
|
|
|
self.route_layer_width = getattr(self, "{}_width".format(self.route_layer))
|
|
|
|
|
self.route_layer_space = getattr(self, "{}_space".format(self.route_layer))
|
|
|
|
|
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
|
|
|
|
|
|
2020-06-24 19:00:00 +02:00
|
|
|
# hack for enclosing input pin with npc
|
|
|
|
|
self.input_pin_vias = []
|
|
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
# This is the space from a S/D contact to the supply rail
|
2020-06-12 00:03:50 +02:00
|
|
|
contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space
|
2020-06-05 11:53:03 +02:00
|
|
|
# This is a poly-to-poly of a flipped cell
|
2020-06-11 01:52:51 +02:00
|
|
|
poly_to_poly_gate_space = self.poly_extend_active + 0.5 * self.poly_space
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
self.top_bottom_space = max(contact_to_vdd_rail_space,
|
|
|
|
|
poly_to_poly_gate_space)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-04-26 20:57:29 +02:00
|
|
|
self.create_netlist()
|
2019-10-06 19:30:16 +02:00
|
|
|
if not OPTS.netlist_only:
|
2019-04-26 20:57:29 +02:00
|
|
|
self.create_layout()
|
2019-05-28 01:32:38 +02:00
|
|
|
self.add_boundary()
|
2019-04-26 20:57:29 +02:00
|
|
|
self.DRC_LVS()
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2019-05-28 01:32:38 +02:00
|
|
|
def create_netlist(self):
|
2019-04-26 20:57:29 +02:00
|
|
|
""" Pure virtual function """
|
2019-10-06 19:30:16 +02:00
|
|
|
debug.error("Must over-ride create_netlist.", -1)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-05-28 01:32:38 +02:00
|
|
|
def create_layout(self):
|
2019-04-26 20:57:29 +02:00
|
|
|
""" Pure virtual function """
|
2019-10-06 19:30:16 +02:00
|
|
|
debug.error("Must over-ride create_layout.", -1)
|
2019-05-28 01:32:38 +02:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
def connect_pin_to_rail(self, inst, pin_name, supply_name):
|
2019-01-17 01:30:31 +01:00
|
|
|
""" Connects a ptx pin to a supply rail. """
|
2020-06-05 11:53:03 +02:00
|
|
|
supply_pin = self.get_pin(supply_name)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
source_pins = inst.get_pins(pin_name)
|
|
|
|
|
for source_pin in source_pins:
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
if supply_name == "gnd":
|
|
|
|
|
height = supply_pin.by() - source_pin.by()
|
|
|
|
|
elif supply_name == "vdd":
|
|
|
|
|
height = supply_pin.uy() - source_pin.by()
|
|
|
|
|
else:
|
|
|
|
|
debug.error("Invalid supply name.", -1)
|
|
|
|
|
|
|
|
|
|
debug.check(supply_pin.layer == source_pin.layer, "Supply pin is not on correct layer.")
|
|
|
|
|
self.add_rect(layer=source_pin.layer,
|
2017-12-12 23:53:19 +01:00
|
|
|
offset=source_pin.ll(),
|
|
|
|
|
height=height,
|
|
|
|
|
width=source_pin.width())
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None):
|
2020-03-24 00:55:38 +01:00
|
|
|
"""
|
2019-10-06 19:30:16 +02:00
|
|
|
Route the input gate to the left side of the cell for access.
|
|
|
|
|
Position specifies to place the contact the left, center, or
|
|
|
|
|
right of gate.
|
|
|
|
|
"""
|
2017-12-12 23:53:19 +01:00
|
|
|
|
|
|
|
|
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!
|
2020-02-04 18:38:35 +01:00
|
|
|
if nmos_gate_pin.ll().x != pmos_gate_pin.ll().x:
|
|
|
|
|
self.gds_write("unaliged_gates.gds")
|
2019-10-06 19:30:16 +02:00
|
|
|
debug.check(nmos_gate_pin.ll().x == pmos_gate_pin.ll().x,
|
2020-02-04 18:38:35 +01:00
|
|
|
"Connecting unaligned gates not supported. See unaligned_gates.gds.")
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-02-10 18:12:39 +01:00
|
|
|
# Pick point on the left of NMOS and up to PMOS
|
|
|
|
|
nmos_gate_pos = nmos_gate_pin.ul() + vector(0.5 * self.poly_width, 0)
|
2019-10-06 19:30:16 +02:00
|
|
|
pmos_gate_pos = vector(nmos_gate_pos.x, pmos_gate_pin.bc().y)
|
|
|
|
|
self.add_path("poly", [nmos_gate_pos, pmos_gate_pos])
|
2017-12-12 23:53:19 +01:00
|
|
|
|
|
|
|
|
# Add the via to the cell midpoint along the gate
|
2019-10-06 19:30:16 +02:00
|
|
|
left_gate_offset = vector(nmos_gate_pin.lx(), ypos)
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2019-10-06 19:30:16 +02:00
|
|
|
# Center is completely symmetric.
|
2022-07-13 19:57:56 +02:00
|
|
|
contact_width = self.poly_contact.width
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-10-06 19:30:16 +02:00
|
|
|
if position == "center":
|
|
|
|
|
contact_offset = left_gate_offset \
|
|
|
|
|
+ vector(0.5 * self.poly_width, 0)
|
|
|
|
|
elif position == "farleft":
|
|
|
|
|
contact_offset = left_gate_offset \
|
2022-07-13 19:57:56 +02:00
|
|
|
- vector(0.5 * self.poly_contact.width, 0)
|
2019-10-06 19:30:16 +02:00
|
|
|
elif position == "left":
|
|
|
|
|
contact_offset = left_gate_offset \
|
|
|
|
|
- vector(0.5 * contact_width - 0.5 * self.poly_width, 0)
|
|
|
|
|
elif position == "right":
|
|
|
|
|
contact_offset = left_gate_offset \
|
2020-02-25 01:34:59 +01:00
|
|
|
+ vector(0.5 * contact_width + 0.5 * self.poly_width, 0)
|
2017-12-12 23:53:19 +01:00
|
|
|
else:
|
|
|
|
|
debug.error("Invalid contact placement option.", -1)
|
|
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
via = self.add_via_stack_center(from_layer="poly",
|
|
|
|
|
to_layer=self.route_layer,
|
|
|
|
|
offset=contact_offset,
|
|
|
|
|
directions=directions)
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2018-03-21 21:20:48 +01:00
|
|
|
self.add_layout_pin_rect_center(text=name,
|
2020-06-05 11:53:03 +02:00
|
|
|
layer=self.route_layer,
|
2017-12-12 23:53:19 +01:00
|
|
|
offset=contact_offset,
|
2020-06-05 11:53:03 +02:00
|
|
|
width=via.mod.second_layer_width,
|
|
|
|
|
height=via.mod.second_layer_height)
|
2019-10-06 19:30:16 +02:00
|
|
|
# This is to ensure that the contact is
|
|
|
|
|
# connected to the gate
|
|
|
|
|
mid_point = contact_offset.scale(0.5, 1) \
|
|
|
|
|
+ left_gate_offset.scale(0.5, 0)
|
2017-12-12 23:53:19 +01:00
|
|
|
self.add_rect_center(layer="poly",
|
|
|
|
|
offset=mid_point,
|
2022-07-13 19:57:56 +02:00
|
|
|
height=self.poly_contact.first_layer_width,
|
2019-10-06 19:30:16 +02:00
|
|
|
width=left_gate_offset.x - contact_offset.x)
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2020-06-24 20:54:59 +02:00
|
|
|
return via
|
2020-06-24 19:00:00 +02:00
|
|
|
|
2020-02-06 17:20:09 +01:00
|
|
|
def extend_wells(self):
|
2017-12-12 23:53:19 +01:00
|
|
|
""" Extend the n/p wells to cover whole cell """
|
|
|
|
|
|
2020-02-06 17:20:09 +01:00
|
|
|
# This should match the cells in the cell library
|
2020-06-24 17:26:15 +02:00
|
|
|
self.nwell_yoffset = 0.48 * self.height
|
2020-06-05 11:53:03 +02:00
|
|
|
full_height = self.height + 0.5 * self.m1_width
|
2020-06-24 20:54:59 +02:00
|
|
|
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-01-24 19:00:28 +01:00
|
|
|
# FIXME: float rounding problem
|
2019-12-23 22:49:47 +01:00
|
|
|
if "nwell" in layer:
|
2020-01-30 04:34:04 +01:00
|
|
|
# Add a rail width to extend the well to the top of the rail
|
|
|
|
|
nwell_max_offset = max(self.find_highest_layer_coords("nwell").y,
|
2020-02-06 17:20:09 +01:00
|
|
|
full_height)
|
2020-06-24 17:26:15 +02:00
|
|
|
nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0)
|
|
|
|
|
nwell_height = nwell_max_offset - self.nwell_yoffset
|
2017-12-12 23:53:19 +01:00
|
|
|
self.add_rect(layer="nwell",
|
2020-02-05 19:22:45 +01:00
|
|
|
offset=nwell_position,
|
2020-06-05 11:53:03 +02:00
|
|
|
width=self.width + 2 * self.well_extend_active,
|
2017-12-12 23:53:19 +01:00
|
|
|
height=nwell_height)
|
2020-01-30 04:34:04 +01:00
|
|
|
if "vtg" in layer:
|
|
|
|
|
self.add_rect(layer="vtg",
|
|
|
|
|
offset=nwell_position,
|
2020-06-05 11:53:03 +02:00
|
|
|
width=self.width + 2 * self.well_extend_active,
|
2020-01-30 04:34:04 +01:00
|
|
|
height=nwell_height)
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2020-01-23 20:43:41 +01:00
|
|
|
# Start this half a rail width below the cell
|
2019-12-23 22:49:47 +01:00
|
|
|
if "pwell" in layer:
|
2020-01-30 04:34:04 +01:00
|
|
|
pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y,
|
|
|
|
|
-0.5 * self.m1_width)
|
2020-02-05 19:22:45 +01:00
|
|
|
pwell_position = vector(-self.well_extend_active, pwell_min_offset)
|
2020-06-24 17:26:15 +02:00
|
|
|
pwell_height = self.nwell_yoffset - pwell_position.y
|
2017-12-12 23:53:19 +01:00
|
|
|
self.add_rect(layer="pwell",
|
2018-01-26 21:39:00 +01:00
|
|
|
offset=pwell_position,
|
2020-06-05 11:53:03 +02:00
|
|
|
width=self.width + 2 * self.well_extend_active,
|
2018-01-11 19:24:44 +01:00
|
|
|
height=pwell_height)
|
2020-01-30 04:34:04 +01:00
|
|
|
if "vtg" in layer:
|
|
|
|
|
self.add_rect(layer="vtg",
|
|
|
|
|
offset=pwell_position,
|
2020-06-05 11:53:03 +02:00
|
|
|
width=self.width + 2 * self.well_extend_active,
|
2020-01-30 04:34:04 +01:00
|
|
|
height=pwell_height)
|
2020-06-24 22:48:30 +02:00
|
|
|
|
2020-10-28 17:54:15 +01:00
|
|
|
if cell_props.pgate.add_implants:
|
2020-06-24 22:48:30 +02:00
|
|
|
self.extend_implants()
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_nwell_contact(self, pmos, pmos_pos):
|
|
|
|
|
""" Add an nwell contact next to the given pmos device. """
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-12-13 23:56:14 +01:00
|
|
|
layer_stack = self.active_stack
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
# To the right a spacing away from the pmos right active edge
|
2019-10-06 19:30:16 +02:00
|
|
|
contact_xoffset = pmos_pos.x + pmos.active_width \
|
2019-12-17 20:03:36 +01:00
|
|
|
+ self.active_space
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2019-10-06 19:30:16 +02:00
|
|
|
# Must be at least an well enclosure of active down
|
|
|
|
|
# from the top of the well
|
2018-01-11 19:24:44 +01:00
|
|
|
# OR align the active with the top of PMOS active.
|
2019-10-06 19:30:16 +02:00
|
|
|
max_y_offset = self.height + 0.5 * self.m1_width
|
2022-02-11 21:09:42 +01:00
|
|
|
contact_yoffset = min(self.height - 0.5 * self.implant_width,
|
|
|
|
|
self.get_tx_insts("pmos")[0].uy()) \
|
|
|
|
|
- pmos.active_contact.first_layer_height \
|
|
|
|
|
- self.implant_enclose_active
|
2018-01-11 19:24:44 +01:00
|
|
|
contact_offset = vector(contact_xoffset, contact_yoffset)
|
2017-12-12 23:53:19 +01:00
|
|
|
# Offset by half a contact in x and y
|
2019-10-06 19:30:16 +02:00
|
|
|
contact_offset += vector(0.5 * pmos.active_contact.first_layer_width,
|
2020-03-24 00:55:38 +01:00
|
|
|
0.5 * pmos.active_contact.first_layer_height)
|
2022-07-13 19:57:56 +02:00
|
|
|
# This over-rides the default one with a custom direction
|
2019-10-06 19:30:16 +02:00
|
|
|
self.nwell_contact = self.add_via_center(layers=layer_stack,
|
|
|
|
|
offset=contact_offset,
|
|
|
|
|
implant_type="n",
|
2020-06-05 11:53:03 +02:00
|
|
|
well_type="n",
|
|
|
|
|
directions=("V", "V"))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
self.add_rect_center(layer=self.route_layer,
|
2020-03-24 00:55:38 +01:00
|
|
|
offset=contact_offset + vector(0, 0.5 * (self.height - contact_offset.y)),
|
2018-01-26 21:39:00 +01:00
|
|
|
width=self.nwell_contact.mod.second_layer_width,
|
2018-01-11 19:24:44 +01:00
|
|
|
height=self.height - contact_offset.y)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
# Now add the full active and implant for the PMOS
|
2019-10-06 19:30:16 +02:00
|
|
|
# active_offset = pmos_pos + vector(pmos.active_width,0)
|
|
|
|
|
# This might be needed if the spacing between the actives
|
|
|
|
|
# is not satisifed
|
2018-01-11 19:24:44 +01:00
|
|
|
# self.add_rect(layer="active",
|
|
|
|
|
# offset=active_offset,
|
|
|
|
|
# width=pmos.active_contact.width,
|
|
|
|
|
# height=pmos.active_height)
|
|
|
|
|
|
2019-10-06 19:30:16 +02:00
|
|
|
# we need to ensure implants don't overlap and are
|
|
|
|
|
# spaced far enough apart
|
2018-01-26 21:39:00 +01:00
|
|
|
# implant_spacing = self.implant_space+self.implant_enclose_active
|
2019-10-06 19:30:16 +02:00
|
|
|
# implant_offset = active_offset + vector(implant_spacing,0) \
|
|
|
|
|
# - vector(0,self.implant_enclose_active)
|
|
|
|
|
# implant_width = pmos.active_contact.width \
|
|
|
|
|
# + 2*self.implant_enclose_active
|
2018-01-26 21:39:00 +01:00
|
|
|
# implant_height = pmos.active_height + 2*self.implant_enclose_active
|
2018-01-11 19:24:44 +01:00
|
|
|
# self.add_rect(layer="nimplant",
|
|
|
|
|
# offset=implant_offset,
|
|
|
|
|
# width=implant_width,
|
|
|
|
|
# height=implant_height)
|
|
|
|
|
|
2018-01-26 21:39:00 +01:00
|
|
|
# Return the top of the well
|
2018-01-11 19:24:44 +01:00
|
|
|
|
2020-06-24 20:54:59 +02:00
|
|
|
def extend_implants(self):
|
|
|
|
|
"""
|
|
|
|
|
Add top-to-bottom implants for adjacency issues in s8.
|
|
|
|
|
"""
|
2020-06-24 21:07:47 +02:00
|
|
|
if self.add_wells:
|
|
|
|
|
rightx = None
|
|
|
|
|
else:
|
|
|
|
|
rightx = self.width
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-24 20:54:59 +02:00
|
|
|
nmos_insts = self.get_tx_insts("nmos")
|
2020-06-24 21:07:47 +02:00
|
|
|
if len(nmos_insts) > 0:
|
|
|
|
|
self.add_enclosure(nmos_insts,
|
|
|
|
|
layer="nimplant",
|
|
|
|
|
extend=self.implant_enclose_active,
|
|
|
|
|
leftx=0,
|
|
|
|
|
rightx=rightx,
|
|
|
|
|
boty=0)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-24 20:54:59 +02:00
|
|
|
pmos_insts = self.get_tx_insts("pmos")
|
2020-06-24 21:07:47 +02:00
|
|
|
if len(pmos_insts) > 0:
|
|
|
|
|
self.add_enclosure(pmos_insts,
|
|
|
|
|
layer="pimplant",
|
|
|
|
|
extend=self.implant_enclose_active,
|
|
|
|
|
leftx=0,
|
|
|
|
|
rightx=rightx,
|
|
|
|
|
topy=self.height)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2022-02-02 18:36:05 +01:00
|
|
|
self.add_rect(layer="pimplant",
|
|
|
|
|
offset=vector(0, self.height - 0.5 * self.implant_width),
|
|
|
|
|
width=self.width,
|
|
|
|
|
height=self.implant_width)
|
|
|
|
|
self.add_rect(layer="nimplant",
|
|
|
|
|
offset=vector(0, -0.5 * self.implant_width),
|
|
|
|
|
width=self.width,
|
|
|
|
|
height=self.implant_width)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# try:
|
|
|
|
|
# ntap_insts = [self.nwell_contact]
|
|
|
|
|
# self.add_enclosure(ntap_insts,
|
|
|
|
|
# layer="nimplant",
|
|
|
|
|
# extend=self.implant_enclose_active,
|
|
|
|
|
# rightx=self.width,
|
|
|
|
|
# topy=self.height)
|
|
|
|
|
# except AttributeError:
|
|
|
|
|
# pass
|
|
|
|
|
# try:
|
|
|
|
|
# ptap_insts = [self.pwell_contact]
|
|
|
|
|
# self.add_enclosure(ptap_insts,
|
|
|
|
|
# layer="pimplant",
|
|
|
|
|
# extend=self.implant_enclose_active,
|
|
|
|
|
# rightx=self.width,
|
|
|
|
|
# boty=0)
|
|
|
|
|
# except AttributeError:
|
|
|
|
|
# pass
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_pwell_contact(self, nmos, nmos_pos):
|
|
|
|
|
""" Add an pwell contact next to the given nmos device. """
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2019-12-13 23:56:14 +01:00
|
|
|
layer_stack = self.active_stack
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
# To the right a spacing away from the nmos right active edge
|
2019-10-06 19:30:16 +02:00
|
|
|
contact_xoffset = nmos_pos.x + nmos.active_width \
|
2019-12-17 20:03:36 +01:00
|
|
|
+ self.active_space
|
2022-02-02 18:36:05 +01:00
|
|
|
# Allow an nimplant below it under the rail
|
2022-07-22 18:52:38 +02:00
|
|
|
contact_yoffset = max(0.5 * self.implant_width + self.implant_enclose_active,
|
2022-02-11 21:09:42 +01:00
|
|
|
self.get_tx_insts("nmos")[0].by())
|
2018-01-11 19:24:44 +01:00
|
|
|
contact_offset = vector(contact_xoffset, contact_yoffset)
|
|
|
|
|
|
2017-12-12 23:53:19 +01:00
|
|
|
# Offset by half a contact
|
2019-10-06 19:30:16 +02:00
|
|
|
contact_offset += vector(0.5 * nmos.active_contact.first_layer_width,
|
|
|
|
|
0.5 * nmos.active_contact.first_layer_height)
|
|
|
|
|
self.pwell_contact= self.add_via_center(layers=layer_stack,
|
|
|
|
|
offset=contact_offset,
|
|
|
|
|
implant_type="p",
|
2020-06-05 11:53:03 +02:00
|
|
|
well_type="p",
|
|
|
|
|
directions=("V", "V"))
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
self.add_rect_center(layer=self.route_layer,
|
2020-03-24 00:55:38 +01:00
|
|
|
offset=contact_offset.scale(1, 0.5),
|
2018-01-26 21:39:00 +01:00
|
|
|
width=self.pwell_contact.mod.second_layer_width,
|
2018-01-11 19:24:44 +01:00
|
|
|
height=contact_offset.y)
|
2020-06-24 20:54:59 +02:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
# Now add the full active and implant for the NMOS
|
2019-10-06 19:30:16 +02:00
|
|
|
# active_offset = nmos_pos + vector(nmos.active_width,0)
|
|
|
|
|
# This might be needed if the spacing between the actives
|
|
|
|
|
# is not satisifed
|
2018-01-11 19:24:44 +01:00
|
|
|
# self.add_rect(layer="active",
|
|
|
|
|
# offset=active_offset,
|
|
|
|
|
# width=nmos.active_contact.width,
|
|
|
|
|
# height=nmos.active_height)
|
|
|
|
|
|
2018-01-26 21:39:00 +01:00
|
|
|
# implant_spacing = self.implant_space+self.implant_enclose_active
|
2019-10-06 19:30:16 +02:00
|
|
|
# implant_offset = active_offset + vector(implant_spacing,0) \
|
|
|
|
|
# - vector(0,self.implant_enclose_active)
|
|
|
|
|
# implant_width = nmos.active_contact.width \
|
|
|
|
|
# + 2*self.implant_enclose_active
|
2018-01-26 21:39:00 +01:00
|
|
|
# implant_height = nmos.active_height + 2*self.implant_enclose_active
|
2018-01-11 19:24:44 +01:00
|
|
|
# self.add_rect(layer="pimplant",
|
|
|
|
|
# offset=implant_offset,
|
|
|
|
|
# width=implant_width,
|
|
|
|
|
# height=implant_height)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
def route_supply_rails(self):
|
|
|
|
|
""" Add vdd/gnd rails to the top and bottom. """
|
|
|
|
|
self.add_layout_pin_rect_center(text="gnd",
|
|
|
|
|
layer=self.route_layer,
|
|
|
|
|
offset=vector(0.5 * self.width, 0),
|
|
|
|
|
width=self.width)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
self.add_layout_pin_rect_center(text="vdd",
|
|
|
|
|
layer=self.route_layer,
|
|
|
|
|
offset=vector(0.5 * self.width, self.height),
|
|
|
|
|
width=self.width)
|
2020-02-25 18:09:07 +01:00
|
|
|
|
|
|
|
|
def determine_width(self):
|
|
|
|
|
""" Determine the width based on the well contacts (assumed to be on the right side) """
|
2020-06-05 11:53:03 +02:00
|
|
|
|
|
|
|
|
# It was already set or is left as default (minimum)
|
2020-03-04 23:23:05 +01:00
|
|
|
# Width is determined by well contact and spacing and allowing a supply via between each cell
|
2020-06-05 11:53:03 +02:00
|
|
|
if self.add_wells:
|
2022-07-13 19:57:56 +02:00
|
|
|
width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * self.m1_via.width
|
2020-06-05 11:53:03 +02:00
|
|
|
# Height is an input parameter, so it is not recomputed.
|
|
|
|
|
else:
|
|
|
|
|
max_active_xoffset = self.find_highest_layer_coords("active").x
|
|
|
|
|
max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space
|
|
|
|
|
width = max(max_active_xoffset, max_route_xoffset)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-06-05 11:53:03 +02:00
|
|
|
self.width = width
|
2020-04-10 04:39:21 +02:00
|
|
|
|
2020-05-06 01:35:51 +02:00
|
|
|
@staticmethod
|
2020-07-16 02:15:42 +02:00
|
|
|
def best_bin(tx_type, target_width):
|
|
|
|
|
"""
|
|
|
|
|
Determine the width transistor that meets the accuracy requirement and is larger than target_width.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Find all of the relavent scaled bins and multiples
|
|
|
|
|
scaled_bins = pgate.scaled_bins(tx_type, target_width)
|
2020-11-03 15:29:17 +01:00
|
|
|
|
2020-07-16 02:15:42 +02:00
|
|
|
for (scaled_width, multiple) in scaled_bins:
|
|
|
|
|
if abs(target_width - scaled_width) / target_width <= 1 - OPTS.accuracy_requirement:
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
debug.error("failed to bin tx size {}, try reducing accuracy requirement".format(target_width), 1)
|
|
|
|
|
|
|
|
|
|
debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type,
|
|
|
|
|
multiple,
|
|
|
|
|
scaled_width / multiple,
|
|
|
|
|
scaled_width,
|
|
|
|
|
target_width))
|
|
|
|
|
|
|
|
|
|
return(scaled_width / multiple, multiple)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def scaled_bins(tx_type, target_width):
|
|
|
|
|
"""
|
|
|
|
|
Determine a set of widths and multiples that could be close to the right size
|
|
|
|
|
sorted by the fewest number of fingers.
|
|
|
|
|
"""
|
2020-04-10 04:39:21 +02:00
|
|
|
if tx_type == "nmos":
|
|
|
|
|
bins = nmos_bins[drc("minwidth_poly")]
|
|
|
|
|
elif tx_type == "pmos":
|
|
|
|
|
bins = pmos_bins[drc("minwidth_poly")]
|
|
|
|
|
else:
|
|
|
|
|
debug.error("invalid tx type")
|
2020-07-16 02:15:42 +02:00
|
|
|
|
|
|
|
|
# Prune out bins that are too big, except for one bigger
|
2020-04-10 04:39:21 +02:00
|
|
|
bins = bins[0:bisect_left(bins, target_width) + 1]
|
2020-07-16 02:15:42 +02:00
|
|
|
|
|
|
|
|
# Determine multiple of target width for each bin
|
2020-04-10 04:39:21 +02:00
|
|
|
if len(bins) == 1:
|
2020-07-16 02:15:42 +02:00
|
|
|
scaled_bins = [(bins[0], math.ceil(target_width / bins[0]))]
|
2020-04-10 04:39:21 +02:00
|
|
|
else:
|
|
|
|
|
scaled_bins = []
|
2020-07-16 02:15:42 +02:00
|
|
|
# Add the biggest size as 1x multiple
|
|
|
|
|
scaled_bins.append((bins[-1], 1))
|
|
|
|
|
# Compute discrete multiple of other sizes
|
|
|
|
|
for width in reversed(bins[:-1]):
|
|
|
|
|
multiple = math.ceil(target_width / width)
|
|
|
|
|
scaled_bins.append((multiple * width, multiple))
|
2020-04-10 04:39:21 +02:00
|
|
|
|
2020-07-16 02:15:42 +02:00
|
|
|
return(scaled_bins)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def nearest_bin(tx_type, target_width):
|
|
|
|
|
"""
|
|
|
|
|
Determine the nearest width to the given target_width
|
|
|
|
|
while assuming a single multiple.
|
|
|
|
|
"""
|
2020-04-18 14:26:39 +02:00
|
|
|
if tx_type == "nmos":
|
|
|
|
|
bins = nmos_bins[drc("minwidth_poly")]
|
|
|
|
|
elif tx_type == "pmos":
|
|
|
|
|
bins = pmos_bins[drc("minwidth_poly")]
|
|
|
|
|
else:
|
2020-07-16 02:15:42 +02:00
|
|
|
debug.error("invalid tx type")
|
|
|
|
|
|
|
|
|
|
# Find the next larger bin
|
|
|
|
|
bin_loc = bisect_left(bins, target_width)
|
|
|
|
|
if bin_loc < len(bins):
|
|
|
|
|
return bins[bin_loc]
|
2020-04-18 14:26:39 +02:00
|
|
|
else:
|
2020-07-16 02:15:42 +02:00
|
|
|
return bins[-1]
|
2020-02-25 18:09:07 +01:00
|
|
|
|
2020-07-16 02:15:42 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def bin_accuracy(ideal_width, width):
|
|
|
|
|
return 1 - abs((ideal_width - width) / ideal_width)
|