mirror of https://github.com/VLSIDA/OpenRAM.git
Pinv implemented, but not DRCed. More new unit tests added for pinv.
This commit is contained in:
parent
13008e1de4
commit
9abe82b203
510
compiler/pinv.py
510
compiler/pinv.py
|
|
@ -1,7 +1,7 @@
|
||||||
import contact
|
import contact
|
||||||
import design
|
import design
|
||||||
import debug
|
import debug
|
||||||
from tech import drc, parameter, spice
|
from tech import drc, parameter, spice, info
|
||||||
from ptx import ptx
|
from ptx import ptx
|
||||||
from vector import vector
|
from vector import vector
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
@ -9,12 +9,12 @@ from globals import OPTS
|
||||||
|
|
||||||
class pinv(design.design):
|
class pinv(design.design):
|
||||||
"""
|
"""
|
||||||
This module generates gds of a parametrically sized inverter. The
|
Pinv generates gds of a parametrically sized inverter. The
|
||||||
size is specified as the nmos size (relative to minimum) and a
|
size is specified as the drive size (relative to minimum NMOS) and
|
||||||
beta value for choosing the pmos size. The inverter's cell_height
|
a beta value for choosing the pmos size. The inverter's cell
|
||||||
is usually the same as the 6t library cell. The route_output will
|
height is usually the same as the 6t library cell and is measured
|
||||||
route the output to the right side of the cell for easier access.
|
from center of rail to rail.. The route_output will route the
|
||||||
|
output to the right side of the cell for easier access.
|
||||||
"""
|
"""
|
||||||
c = reload(__import__(OPTS.config.bitcell))
|
c = reload(__import__(OPTS.config.bitcell))
|
||||||
bitcell = getattr(c, OPTS.config.bitcell)
|
bitcell = getattr(c, OPTS.config.bitcell)
|
||||||
|
|
@ -58,22 +58,22 @@ class pinv(design.design):
|
||||||
self.determine_tx_mults()
|
self.determine_tx_mults()
|
||||||
self.create_ptx()
|
self.create_ptx()
|
||||||
self.setup_layout_constants()
|
self.setup_layout_constants()
|
||||||
self.add_rails()
|
self.add_supply_rails()
|
||||||
self.add_ptx()
|
self.add_ptx()
|
||||||
|
|
||||||
# These aren't for instantiating, but we use them to get the dimensions
|
# These aren't for instantiating, but we use them to get the dimensions
|
||||||
self.nwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"),
|
# self.nwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"),
|
||||||
dimensions=(1, self.pmos.num_contacts))
|
# dimensions=(1, self.pmos.num_contacts))
|
||||||
self.pwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"),
|
# self.pwell_contact = contact.contact(layer_stack=("active", "contact", "metal1"),
|
||||||
dimensions=(1, self.nmos.num_contacts))
|
# dimensions=(1, self.nmos.num_contacts))
|
||||||
|
|
||||||
self.extend_wells()
|
self.extend_wells()
|
||||||
self.extend_active()
|
#self.extend_active()
|
||||||
self.add_well_contacts()
|
self.add_well_contacts()
|
||||||
self.connect_well_contacts()
|
|
||||||
self.connect_rails()
|
self.connect_rails()
|
||||||
self.connect_tx()
|
self.route_input_gate()
|
||||||
self.route_pins()
|
self.route_output_drain()
|
||||||
|
|
||||||
|
|
||||||
def determine_tx_mults(self):
|
def determine_tx_mults(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,9 +94,10 @@ class pinv(design.design):
|
||||||
|
|
||||||
# Determine the height left to the transistors to determine the number of fingers
|
# Determine the height left to the transistors to determine the number of fingers
|
||||||
tx_height_available = self.height - metal_height
|
tx_height_available = self.height - metal_height
|
||||||
# Divide the height according to beta
|
# Divide the height in half. Could divide proportional to beta, but this makes
|
||||||
nmos_height_available = 1.0/(self.beta+1) * tx_height_available
|
# connecting wells of multiple cells easier.
|
||||||
pmos_height_available = tx_height_available - nmos_height_available
|
nmos_height_available = 0.5 * tx_height_available
|
||||||
|
pmos_height_available = 0.5 * tx_height_available
|
||||||
|
|
||||||
|
|
||||||
# Determine the number of mults for each to fit width into available space
|
# Determine the number of mults for each to fit width into available space
|
||||||
|
|
@ -114,313 +115,274 @@ class pinv(design.design):
|
||||||
debug.check(self.nmos_width>=drc["minwidth_tx"],"Cannot finger NMOS transistors to fit cell height.")
|
debug.check(self.nmos_width>=drc["minwidth_tx"],"Cannot finger NMOS transistors to fit cell height.")
|
||||||
self.pmos_width = self.pmos_width / self.tx_mults
|
self.pmos_width = self.pmos_width / self.tx_mults
|
||||||
debug.check(self.pmos_width>=drc["minwidth_tx"],"Cannot finger PMOS transistors to fit cell height.")
|
debug.check(self.pmos_width>=drc["minwidth_tx"],"Cannot finger PMOS transistors to fit cell height.")
|
||||||
|
|
||||||
|
def setup_layout_constants(self):
|
||||||
|
"""
|
||||||
|
Pre-compute some handy layout parameters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.m1_width = drc["minwidth_metal1"]
|
||||||
|
|
||||||
|
# the well width is determined the multi-finger PMOS device width plus
|
||||||
|
# the well contact width and half well enclosure on both sides
|
||||||
|
self.well_width = self.pmos.active_width + self.pmos.active_contact.width \
|
||||||
|
+ drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
|
||||||
|
self.width = self.well_width
|
||||||
|
# Height is an input parameter
|
||||||
|
|
||||||
|
# This will help with the wells and the input/output placement
|
||||||
|
self.middle_position = vector(0,0.5*self.height)
|
||||||
|
|
||||||
|
|
||||||
def create_ptx(self):
|
def create_ptx(self):
|
||||||
"""Intiializes a ptx object"""
|
""" Create the PMOS and NMOS transistors. """
|
||||||
self.nmos = ptx(width=self.nmos_width,
|
self.nmos = ptx(width=self.nmos_width,
|
||||||
mults=self.tx_mults,
|
mults=self.tx_mults,
|
||||||
tx_type="nmos")
|
tx_type="nmos",
|
||||||
|
connect_poly=True,
|
||||||
|
connect_active=True)
|
||||||
self.add_mod(self.nmos)
|
self.add_mod(self.nmos)
|
||||||
self.pmos = ptx(width=self.pmos_width,
|
self.pmos = ptx(width=self.pmos_width,
|
||||||
mults=self.tx_mults,
|
mults=self.tx_mults,
|
||||||
tx_type="pmos")
|
tx_type="pmos",
|
||||||
|
connect_poly=True,
|
||||||
|
connect_active=True)
|
||||||
self.add_mod(self.pmos)
|
self.add_mod(self.pmos)
|
||||||
|
|
||||||
|
def add_supply_rails(self):
|
||||||
|
""" Add vdd/gnd rails to the top and bottom. """
|
||||||
|
self.add_center_layout_pin_rect(text="gnd",
|
||||||
|
layer="metal1",
|
||||||
|
offset=vector(0.5*self.width,0),
|
||||||
|
width=self.width)
|
||||||
|
|
||||||
def setup_layout_constants(self):
|
self.add_center_layout_pin_rect(text="vdd",
|
||||||
"""Sets up constant variables"""
|
layer="metal1",
|
||||||
# the well width is determined the multi-finger PMOS device width plus
|
offset=vector(0.5*self.width,self.height),
|
||||||
# the well contact width and enclosure
|
width=self.width)
|
||||||
self.well_width = self.pmos.active_position.x \
|
|
||||||
+ self.pmos.active_width \
|
|
||||||
+ drc["active_to_body_active"] \
|
|
||||||
+ self.pmos.active_contact.width \
|
|
||||||
+ drc["well_enclosure_active"]
|
|
||||||
self.width = self.well_width
|
|
||||||
|
|
||||||
def add_rails(self):
|
|
||||||
"""Adds vdd/gnd rails to the layout"""
|
|
||||||
rail_width = self.width
|
|
||||||
rail_height = self.rail_height = drc["minwidth_metal1"]
|
|
||||||
|
|
||||||
self.gnd_position = vector(0, - 0.5 * drc["minwidth_metal1"]) # for tiling purposes
|
|
||||||
self.add_layout_pin(text="gnd",
|
|
||||||
layer="metal1",
|
|
||||||
offset=self.gnd_position,
|
|
||||||
width=rail_width,
|
|
||||||
height=rail_height)
|
|
||||||
|
|
||||||
self.vdd_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"])
|
|
||||||
self.add_layout_pin(text="vdd",
|
|
||||||
layer="metal1",
|
|
||||||
offset=self.vdd_position,
|
|
||||||
width=rail_width,
|
|
||||||
height=rail_height)
|
|
||||||
|
|
||||||
def add_ptx(self):
|
def add_ptx(self):
|
||||||
"""Adds pmos and nmos to the layout"""
|
"""
|
||||||
# determines the spacing between the edge and nmos (rail to active
|
Add PMOS and NMOS to the layout at the upper-most and lowest position
|
||||||
# metal or poly_to_poly spacing)
|
to provide maximum routing in channel
|
||||||
edge_to_nmos = max(drc["metal1_to_metal1"] \
|
"""
|
||||||
- self.nmos.active_contact_positions[0].y,
|
|
||||||
0.5 * drc["poly_to_poly"] \
|
|
||||||
- 0.5 * drc["minwidth_metal1"] \
|
|
||||||
- self.nmos.poly_positions[0].y)
|
|
||||||
self.nmos_position = vector(0, 0.5 * drc["minwidth_metal1"] + edge_to_nmos)
|
|
||||||
offset = self.nmos_position + vector(0,self.nmos.height)
|
|
||||||
self.add_inst(name="pinv_nmos",
|
|
||||||
mod=self.nmos,
|
|
||||||
offset=offset,
|
|
||||||
mirror="MX")
|
|
||||||
self.connect_inst(["Z", "A", "gnd", "gnd"])
|
|
||||||
|
|
||||||
# determines the spacing between the edge and pmos
|
# place PMOS so it is half a poly spacing down from the top
|
||||||
edge_to_pmos = max(drc["metal1_to_metal1"] \
|
# Source should overlap rail if it is fingered!
|
||||||
- self.pmos.active_contact_positions[0].y,
|
pmos_position = vector(0,self.height-self.pmos.height-0.5*drc["poly_to_poly"])
|
||||||
0.5 * drc["poly_to_poly"] \
|
self.pmos_inst=self.add_inst(name="pinv_pmos",
|
||||||
- 0.5 * drc["minwidth_metal1"] \
|
mod=self.pmos,
|
||||||
- self.pmos.poly_positions[0].y)
|
offset=pmos_position)
|
||||||
self.pmos_position = vector(0,
|
|
||||||
self.height - 0.5 * drc["minwidth_metal1"]
|
|
||||||
- edge_to_pmos - self.pmos.height)
|
|
||||||
self.add_inst(name="pinv_pmos",
|
|
||||||
mod=self.pmos,
|
|
||||||
offset=self.pmos_position)
|
|
||||||
self.connect_inst(["Z", "A", "vdd", "vdd"])
|
self.connect_inst(["Z", "A", "vdd", "vdd"])
|
||||||
|
|
||||||
|
# place NMOS so that it is half a poly spacing up from the bottom
|
||||||
|
# Source should overlap rail if it is fingered!
|
||||||
|
nmos_position = vector(0,self.nmos.height+0.5*drc["poly_to_poly"])
|
||||||
|
self.nmos_inst=self.add_inst(name="pinv_nmos",
|
||||||
|
mod=self.nmos,
|
||||||
|
offset=nmos_position,
|
||||||
|
mirror="MX")
|
||||||
|
self.connect_inst(["Z", "A", "gnd", "gnd"])
|
||||||
|
|
||||||
|
|
||||||
def extend_wells(self):
|
def extend_wells(self):
|
||||||
"""Extends the n/p wells to cover whole layout"""
|
""" Extend the n/p wells to cover whole cell """
|
||||||
nmos_top_yposition = self.nmos_position.y + self.nmos.height
|
|
||||||
# calculates the length between the pmos and nmos
|
|
||||||
middle_length = self.pmos_position.y - nmos_top_yposition
|
|
||||||
# calculate the middle point between the pmos and nmos
|
|
||||||
middle_yposition = nmos_top_yposition + 0.5 * middle_length
|
|
||||||
|
|
||||||
self.nwell_position = vector(0, middle_yposition)
|
nwell_height = self.height - self.middle_position.y
|
||||||
self.nwell_height = self.height - middle_yposition
|
if info["has_nwell"]:
|
||||||
self.add_rect(layer="nwell",
|
self.add_rect(layer="nwell",
|
||||||
offset=self.nwell_position,
|
offset=self.middle_position,
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
height=self.nwell_height)
|
height=nwell_height)
|
||||||
self.add_rect(layer="vtg",
|
self.add_rect(layer="vtg",
|
||||||
offset=self.nwell_position,
|
offset=self.middle_position,
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
height=self.nwell_height)
|
height=nwell_height)
|
||||||
|
|
||||||
self.pwell_position = vector(0, 0)
|
if info["has_pwell"]:
|
||||||
self.pwell_height = middle_yposition
|
self.add_rect(layer="pwell",
|
||||||
self.add_rect(layer="pwell",
|
offset=vector(0,0),
|
||||||
offset=self.pwell_position, width=self.well_width,
|
width=self.well_width,
|
||||||
height=self.pwell_height)
|
height=self.middle_position.y)
|
||||||
self.add_rect(layer="vtg",
|
self.add_rect(layer="vtg",
|
||||||
offset=self.pwell_position,
|
offset=vector(0,0),
|
||||||
width=self.well_width,
|
width=self.well_width,
|
||||||
height=self.pwell_height)
|
height=self.middle_position.y)
|
||||||
|
|
||||||
def extend_active(self):
|
# def extend_active(self):
|
||||||
"""Extends the active area for n/p mos for the addition of the n/p well taps"""
|
# """Extends the active area for n/p mos for the addition of the n/p well taps"""
|
||||||
# calculates the new active width that includes the well_taps
|
# # calculates the new active width that includes the well_taps
|
||||||
self.active_width = self.pmos.active_width \
|
# self.active_width = self.pmos.active_width \
|
||||||
+ drc["active_to_body_active"] \
|
# + drc["active_to_body_active"] \
|
||||||
+ self.pmos.active_contact.width
|
# + self.pmos.active_contact.width
|
||||||
|
|
||||||
# Calculates the coordinates of the bottom left corner of active area
|
# # Calculates the coordinates of the bottom left corner of active area
|
||||||
# of the pmos
|
# # of the pmos
|
||||||
offset = self.pmos_position + self.pmos.active_position
|
# offset = self.pmos_position + self.pmos.active_position
|
||||||
self.add_rect(layer="active",
|
# self.add_rect(layer="active",
|
||||||
offset=offset,
|
# offset=offset,
|
||||||
width=self.active_width,
|
# width=self.active_width,
|
||||||
height=self.pmos.active_height)
|
# height=self.pmos.active_height)
|
||||||
|
|
||||||
# Determines where the active of the well portion starts to add the
|
# # Determines where the active of the well portion starts to add the
|
||||||
# implant
|
# # implant
|
||||||
offset = offset + vector(self.pmos.active_width,0)
|
# offset = offset + vector(self.pmos.active_width,0)
|
||||||
implant_width = self.active_width - self.pmos.active_width
|
# implant_width = self.active_width - self.pmos.active_width
|
||||||
self.add_rect(layer="nimplant",
|
# self.add_rect(layer="nimplant",
|
||||||
offset=offset,
|
# offset=offset,
|
||||||
width=implant_width,
|
# width=implant_width,
|
||||||
height=self.pmos.active_height)
|
# height=self.pmos.active_height)
|
||||||
|
|
||||||
# Calculates the coordinates of the bottom left corner of active area
|
# # Calculates the coordinates of the bottom left corner of active area
|
||||||
# of the nmos
|
# # of the nmos
|
||||||
offset = self.nmos_position + self.nmos.active_position
|
# offset = self.nmos_position + self.nmos.active_position
|
||||||
self.add_rect(layer="active",
|
# self.add_rect(layer="active",
|
||||||
offset=offset,
|
# offset=offset,
|
||||||
width=self.active_width,
|
# width=self.active_width,
|
||||||
height=self.nmos.active_height)
|
# height=self.nmos.active_height)
|
||||||
|
|
||||||
# Determines where the active of the well portion starts to add the
|
# # Determines where the active of the well portion starts to add the
|
||||||
# implant
|
# # implant
|
||||||
offset = offset + vector(self.pmos.active_width,0)
|
# offset = offset + vector(self.pmos.active_width,0)
|
||||||
implant_width = self.active_width - self.nmos.active_width
|
# implant_width = self.active_width - self.nmos.active_width
|
||||||
self.add_rect(layer="pimplant",
|
# self.add_rect(layer="pimplant",
|
||||||
offset=offset,
|
# offset=offset,
|
||||||
width=implant_width,
|
# width=implant_width,
|
||||||
height=self.nmos.active_height)
|
# height=self.nmos.active_height)
|
||||||
|
|
||||||
def connect_poly(self):
|
|
||||||
"""Connects the poly from nmos to pmos (as well if it is multi-fingered)"""
|
|
||||||
# Calculates the y-coordinate of the top of the poly of the nmos
|
|
||||||
nmos_top_poly_yposition = self.nmos_position.y \
|
|
||||||
+ self.nmos.height \
|
|
||||||
- self.nmos.poly_positions[0].y
|
|
||||||
|
|
||||||
poly_length = self.pmos_position.y + self.pmos.poly_positions[0].y \
|
|
||||||
- nmos_top_poly_yposition
|
|
||||||
for position in self.pmos.poly_positions:
|
|
||||||
offset = [position.x, nmos_top_poly_yposition]
|
|
||||||
self.add_rect(layer="poly",
|
|
||||||
offset=offset,
|
|
||||||
width=drc["minwidth_poly"],
|
|
||||||
height=poly_length)
|
|
||||||
|
|
||||||
def connect_drains(self):
|
|
||||||
"""Connects the drains of the nmos/pmos together"""
|
|
||||||
# Determines the top y-coordinate of the nmos drain metal layer
|
|
||||||
yoffset = self.nmos.height \
|
|
||||||
- 0.5 * drc["minwidth_metal1"] \
|
|
||||||
- self.nmos.active_contact_positions[0].y
|
|
||||||
drain_length = self.height - yoffset + drc["minwidth_metal1"] \
|
|
||||||
- (self.pmos.height
|
|
||||||
- self.pmos.active_contact_positions[0].y)
|
|
||||||
for position in self.pmos.active_contact_positions[1:][::2]:
|
|
||||||
offset = [position.x + self.pmos.active_contact.second_layer_position.x,
|
|
||||||
yoffset]
|
|
||||||
self.drain_position = vector(offset)
|
|
||||||
self.add_rect(layer="metal1",
|
|
||||||
offset=offset,
|
|
||||||
width=self.nmos.active_contact.second_layer_width,
|
|
||||||
height=drain_length)
|
|
||||||
|
|
||||||
def route_input_gate(self):
|
def route_input_gate(self):
|
||||||
"""Routes the input gate to the left side of the cell for access"""
|
"""Routes the input gate to the left side of the cell for access"""
|
||||||
|
nmos_gate_pin = self.nmos_inst.get_pin("G")
|
||||||
|
pmos_gate_pin = self.pmos_inst.get_pin("G")
|
||||||
|
|
||||||
xoffset = self.pmos.poly_positions[0].x
|
# Pick point in center of NMOS and connect down to PMOS
|
||||||
# Determines the y-coordinate of where to place the gate input poly pin
|
nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5*drc["minwidth_poly"],0)
|
||||||
# (middle in between the pmos and nmos)
|
pmos_gate_pos = vector(nmos_gate_pos.x,pmos_gate_pin.bc().y)
|
||||||
yoffset = self.nmos.height + (self.height
|
self.add_path("poly",[nmos_gate_pos,pmos_gate_pos])
|
||||||
- self.pmos.height - self.nmos.height
|
|
||||||
- self.poly_contact.width) / 2
|
|
||||||
offset = [xoffset, yoffset]
|
|
||||||
self.add_contact(layers=("poly", "contact", "metal1"),
|
|
||||||
offset=offset,
|
|
||||||
rotate=90)
|
|
||||||
|
|
||||||
# Determines the poly coordinate to connect to the poly contact
|
# The midpoint of the vertical poly gate
|
||||||
offset = offset - self.poly_contact.first_layer_position.rotate_scale(1,0)
|
mid_gate_offset = vector(nmos_gate_pos.x,self.middle_position.y)
|
||||||
self.add_rect(layer="poly",
|
contact_offset = mid_gate_offset - vector(0.5*self.poly_contact.height,0)
|
||||||
offset=offset,
|
self.add_center_contact(layers=("poly", "contact", "metal1"),
|
||||||
width=self.poly_contact.first_layer_position.y + drc["minwidth_poly"],
|
offset=contact_offset,
|
||||||
height=self.poly_contact.first_layer_width)
|
rotate=90)
|
||||||
|
self.add_center_layout_pin_segment(text="A",
|
||||||
|
layer="metal1",
|
||||||
|
start=mid_gate_offset.scale(0,1),
|
||||||
|
end=mid_gate_offset)
|
||||||
|
|
||||||
input_length = self.pmos.poly_positions[0].x - self.poly_contact.height
|
# This is to ensure that the contact is connected to the gate
|
||||||
# Determine the y-coordinate for the placement of the metal1 via
|
mid_point = contact_offset.scale(0.5,1)+mid_gate_offset.scale(0.5,0)
|
||||||
self.input_position = vector(0, 0.5*(self.height - drc["minwidth_metal1"]
|
self.add_center_rect(layer="poly",
|
||||||
+ self.nmos.height - self.pmos.height))
|
offset=mid_point,
|
||||||
self.add_layout_pin(text="A",
|
height=self.poly_contact.first_layer_width,
|
||||||
layer="metal1",
|
width=mid_gate_offset.x-contact_offset.x)
|
||||||
offset=self.input_position,
|
|
||||||
width=input_length,
|
|
||||||
height=drc["minwidth_metal1"])
|
|
||||||
|
|
||||||
def route_output_drain(self):
|
def route_output_drain(self):
|
||||||
"""Routes the output (drain) to the right side of the cell for access"""
|
""" Route the output (drains) together. Optionally, routes output to edge. """
|
||||||
# Determines the y-coordinate of the output metal1 via pin
|
|
||||||
offset = vector(self.drain_position.x
|
# Get the drain pins
|
||||||
+ self.nmos.active_contact.second_layer_width,
|
nmos_drain_pin = self.nmos_inst.get_pin("D")
|
||||||
self.input_position.y)
|
pmos_drain_pin = self.pmos_inst.get_pin("D")
|
||||||
output_length = self.width - offset.x
|
|
||||||
|
# Pick point in center of NMOS and connect down to PMOS
|
||||||
|
nmos_drain_pos = nmos_drain_pin.uc()
|
||||||
|
pmos_drain_pos = vector(nmos_drain_pin.uc().x,pmos_drain_pin.bc().y)
|
||||||
|
self.add_path("metal1",[nmos_drain_pos,pmos_drain_pos])
|
||||||
|
|
||||||
|
# Remember the mid for the output
|
||||||
|
mid_drain_offset = vector(nmos_drain_pos.x,self.middle_position.y)
|
||||||
|
output_length = self.width - mid_drain_offset.x
|
||||||
|
|
||||||
if self.route_output == True:
|
if self.route_output == True:
|
||||||
# This extends the output to the edge of the cell
|
# This extends the output to the edge of the cell
|
||||||
self.add_layout_pin(text="Z",
|
output_offset = mid_drain_offset.scale(0,1) + vector(self.width,0)
|
||||||
layer="metal1",
|
self.add_center_layout_pin_segment(text="Z",
|
||||||
offset=offset,
|
layer="metal1",
|
||||||
width=output_length,
|
start=mid_drain_offset,
|
||||||
height=drc["minwidth_metal1"])
|
end=output_offset)
|
||||||
else:
|
else:
|
||||||
# This leaves the output as an internal pin (min sized)
|
# This leaves the output as an internal pin (min sized)
|
||||||
self.add_layout_pin(text="Z",
|
self.add_layout_pin(text="Z",
|
||||||
layer="metal1",
|
layer="metal1",
|
||||||
offset=offset)
|
offset=mid_drain_offset)
|
||||||
|
|
||||||
|
|
||||||
def add_well_contacts(self):
|
def add_well_contacts(self):
|
||||||
"""Adds n/p well taps to the layout"""
|
""" Add n/p well taps to the layout and connect to supplies"""
|
||||||
|
|
||||||
layer_stack = ("active", "contact", "metal1")
|
layer_stack = ("active", "contact", "metal1")
|
||||||
# Same y-positions of the drain/source metals as the n/p mos
|
|
||||||
well_contact_offset = vector(self.pmos.active_position.x
|
# Lower left of the NMOS active
|
||||||
+ self.active_width
|
nmos_pos = self.nmos_inst.ll() + self.nmos.active_offset
|
||||||
- self.nwell_contact.width,
|
# To the right a spacing away from the nmos right active edge
|
||||||
self.pmos.active_contact_positions[0].y)
|
nwell_contact_offset = self.nmos.active_width + drc["active_to_body_active"]
|
||||||
self.nwell_contact_position = self.pmos_position + well_contact_offset
|
nwell_offset = nmos_pos + vector(nwell_contact_offset, 0)
|
||||||
self.nwell_contact=self.add_contact(layer_stack,self.nwell_contact_position,(1,self.pmos.num_contacts))
|
# Offset by half a contact in x and y
|
||||||
|
nwell_offset += vector(0.5*self.nmos.active_contact.first_layer_width,
|
||||||
|
0.5*self.nmos.active_contact.first_layer_height)
|
||||||
|
self.nwell_contact=self.add_center_contact(layers=layer_stack,
|
||||||
|
offset=nwell_offset,
|
||||||
|
size=(1,self.nmos.num_contacts))
|
||||||
|
self.add_path("metal1",[nwell_offset,nwell_offset.scale(1,0)])
|
||||||
|
# Now add the full active and implant for the PMOS
|
||||||
|
nwell_offset = nmos_pos + vector(self.nmos.active_width,0)
|
||||||
|
nwell_contact_width = drc["active_to_body_active"] + self.nmos.active_contact.width
|
||||||
|
self.add_rect(layer="active",
|
||||||
|
offset=nwell_offset,
|
||||||
|
width=nwell_contact_width,
|
||||||
|
height=self.nmos.active_height)
|
||||||
|
self.add_rect(layer="pimplant",
|
||||||
|
offset=nwell_offset,
|
||||||
|
width=nwell_contact_width,
|
||||||
|
height=self.nmos.active_height)
|
||||||
|
|
||||||
well_contact_offset = vector(self.nmos.active_position.x
|
|
||||||
+ self.active_width
|
|
||||||
- self.pwell_contact.width,
|
|
||||||
self.nmos.active_contact_positions[0].y)
|
|
||||||
self.pwell_contact_position = self.nmos_position + well_contact_offset
|
|
||||||
self.pwell_contact=self.add_contact(layer_stack,self.pwell_contact_position,(1,self.nmos.num_contacts))
|
|
||||||
|
|
||||||
def connect_well_contacts(self):
|
|
||||||
"""Connects the well taps to its respective power rails"""
|
# Lower left of the PMOS active
|
||||||
# calculates the length needed to connect the nwell_tap to vdd
|
pmos_pos = self.pmos_inst.ll() + self.pmos.active_offset
|
||||||
nwell_tap_length = self.height \
|
pwell_contact_offset = self.pmos.active_width + drc["active_to_body_active"]
|
||||||
- 0.5 * drc["minwidth_metal1"] \
|
# To the right a spacing away from the pmos right active edge
|
||||||
- self.nwell_contact_position.y
|
pwell_offset = pmos_pos + vector(pwell_contact_offset, 0)
|
||||||
# obtains the position for the metal 1 layer in the nwell_tap
|
# Offset by half a contact
|
||||||
offset = self.nwell_contact_position + \
|
pwell_offset += vector(0.5*self.pmos.active_contact.first_layer_width,
|
||||||
self.nwell_contact.second_layer_position.scale(1,0)
|
0.5*self.pmos.active_contact.first_layer_height)
|
||||||
self.add_rect(layer="metal1",
|
self.pwell_contact=self.add_center_contact(layers=layer_stack,
|
||||||
offset=offset, width=self.nwell_contact.second_layer_width,
|
offset=pwell_offset,
|
||||||
height=nwell_tap_length)
|
size=(1,self.pmos.num_contacts))
|
||||||
|
self.add_path("metal1",[pwell_offset,vector(pwell_offset.x,self.height)])
|
||||||
|
# Now add the full active and implant for the PMOS
|
||||||
|
pwell_offset = pmos_pos + vector(self.pmos.active_width,0)
|
||||||
|
pwell_contact_width = drc["active_to_body_active"] + self.pmos.active_contact.width
|
||||||
|
self.add_rect(layer="active",
|
||||||
|
offset=pwell_offset,
|
||||||
|
width=pwell_contact_width,
|
||||||
|
height=self.pmos.active_height)
|
||||||
|
self.add_rect(layer="nimplant",
|
||||||
|
offset=pwell_offset,
|
||||||
|
width=pwell_contact_width,
|
||||||
|
height=self.pmos.active_height)
|
||||||
|
|
||||||
|
|
||||||
pwell_tap_length = self.pwell_contact_position.y \
|
|
||||||
+ 0.5 * drc["minwidth_metal1"]
|
|
||||||
offset = [self.pwell_contact_position.x
|
|
||||||
+ self.pwell_contact.second_layer_position.x,
|
|
||||||
0.5 * drc["minwidth_metal1"]]
|
|
||||||
self.add_rect(layer="metal1",
|
|
||||||
offset=offset,
|
|
||||||
width=self.pwell_contact.second_layer_width,
|
|
||||||
height=pwell_tap_length)
|
|
||||||
|
|
||||||
def connect_rails(self):
|
def connect_rails(self):
|
||||||
"""Connects the n/p mos to its respective power rails"""
|
""" Connect the nmos and pmos to its respective power rails """
|
||||||
if self.tx_mults != 1:
|
|
||||||
return
|
nmos_source_pin = self.nmos_inst.get_pin("S")
|
||||||
# corrects offset to obtain real coordinates of the layer in question
|
|
||||||
# in a contact object
|
|
||||||
correct = vector(self.nmos.active_contact.width
|
|
||||||
- self.nmos.active_contact.width,
|
|
||||||
- drc["minwidth_metal1"]).scale(.5,.5)
|
|
||||||
# nmos position of the source metals
|
|
||||||
noffset = self.nmos_position + self.nmos.active_contact_positions[0] + correct
|
|
||||||
offset = [self.nmos.active_contact.second_layer_position.x + noffset.x,
|
|
||||||
0.5 * drc["minwidth_metal1"]]
|
|
||||||
self.add_rect(layer="metal1", offset=offset,
|
|
||||||
width=self.nmos.active_contact.second_layer_width,
|
|
||||||
height=noffset.y)
|
|
||||||
# corrects offset to obtain real coordinates of the layer in question
|
|
||||||
# in a contact object
|
|
||||||
correct = vector(self.pmos.active_contact.width
|
|
||||||
- self.pmos.active_contact.width,
|
|
||||||
self.pmos.active_contact.second_layer_height).scale(.5,1)
|
|
||||||
# pmos position of the source metals
|
|
||||||
offset = self.pmos_position + self.pmos.active_contact_positions[0]\
|
|
||||||
+ correct + self.pmos.active_contact.second_layer_position
|
|
||||||
temp_height = self.height - offset.y - 0.5 * drc["minwidth_metal1"]
|
|
||||||
self.add_rect(layer="metal1",
|
self.add_rect(layer="metal1",
|
||||||
offset=offset,
|
offset=nmos_source_pin.ul(),
|
||||||
width=self.pmos.active_contact.second_layer_width,
|
height=nmos_source_pin.ul().y,
|
||||||
height=temp_height)
|
width=nmos_source_pin.width())
|
||||||
|
|
||||||
def connect_tx(self):
|
pmos_source_pin = self.pmos_inst.get_pin("S")
|
||||||
self.connect_poly()
|
vdd_pin = self.get_pin("vdd")
|
||||||
self.connect_drains()
|
self.add_rect(layer="metal1",
|
||||||
|
offset=pmos_source_pin.ll(),
|
||||||
def route_pins(self):
|
height=vdd_pin.ll().y-pmos_source_pin.ll().y,
|
||||||
self.route_input_gate()
|
width=pmos_source_pin.width())
|
||||||
self.route_output_drain()
|
|
||||||
|
|
||||||
def input_load(self):
|
def input_load(self):
|
||||||
return ((self.nmos_size+self.pmos_size)/parameter["min_tx_size"])*spice["min_tx_gate_c"]
|
return ((self.nmos_size+self.pmos_size)/parameter["min_tx_size"])*spice["min_tx_gate_c"]
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ class ptx(design.design):
|
||||||
dimensions=(1, self.num_contacts))
|
dimensions=(1, self.num_contacts))
|
||||||
|
|
||||||
# Standard DRC rules
|
# Standard DRC rules
|
||||||
self.active_width = drc["minwidth_active"]
|
self.min_active_width = drc["minwidth_active"]
|
||||||
self.contact_width = drc["minwidth_contact"]
|
self.contact_width = drc["minwidth_contact"]
|
||||||
self.poly_width = drc["minwidth_poly"]
|
self.poly_width = drc["minwidth_poly"]
|
||||||
self.poly_to_active = drc["poly_to_active"]
|
self.poly_to_active = drc["poly_to_active"]
|
||||||
|
|
@ -111,7 +111,7 @@ class ptx(design.design):
|
||||||
|
|
||||||
# The enclosure of an active contact. Not sure about second term.
|
# The enclosure of an active contact. Not sure about second term.
|
||||||
active_enclose_contact = max(drc["active_enclosure_contact"],
|
active_enclose_contact = max(drc["active_enclosure_contact"],
|
||||||
(self.active_width - self.contact_width)/2)
|
(self.min_active_width - self.contact_width)/2)
|
||||||
# This is the distance from the edge of poly to the contacted end of active
|
# This is the distance from the edge of poly to the contacted end of active
|
||||||
self.end_to_poly = active_enclose_contact + self.contact_width + drc["contact_to_poly"]
|
self.end_to_poly = active_enclose_contact + self.contact_width + drc["contact_to_poly"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
|
"""
|
||||||
|
Run regresion tests on a parameterized inverter
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from testutils import header
|
||||||
|
import sys,os
|
||||||
|
sys.path.append(os.path.join(sys.path[0],".."))
|
||||||
|
import globals
|
||||||
|
import debug
|
||||||
|
import verify
|
||||||
|
|
||||||
|
OPTS = globals.OPTS
|
||||||
|
|
||||||
|
#@unittest.skip("SKIPPING 04_pinv_test")
|
||||||
|
|
||||||
|
|
||||||
|
class pinv_test(unittest.TestCase):
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
|
||||||
|
OPTS.check_lvsdrc = False
|
||||||
|
|
||||||
|
import pinv
|
||||||
|
import tech
|
||||||
|
|
||||||
|
debug.info(2, "Checking 2x size inverter")
|
||||||
|
tx = pinv.pinv(size=2,beta=4)
|
||||||
|
self.local_check(tx)
|
||||||
|
|
||||||
|
OPTS.check_lvsdrc = True
|
||||||
|
globals.end_openram()
|
||||||
|
|
||||||
|
def local_check(self, tx):
|
||||||
|
tempspice = OPTS.openram_temp + "temp.sp"
|
||||||
|
tempgds = OPTS.openram_temp + "temp.gds"
|
||||||
|
|
||||||
|
tx.sp_write(tempspice)
|
||||||
|
tx.gds_write(tempgds)
|
||||||
|
|
||||||
|
self.assertFalse(verify.run_drc(tx.name, tempgds))
|
||||||
|
self.assertFalse(verify.run_lvs(tx.name, tempgds, tempspice))
|
||||||
|
|
||||||
|
os.remove(tempspice)
|
||||||
|
os.remove(tempgds)
|
||||||
|
|
||||||
|
# reset the static duplicate name checker for unit tests
|
||||||
|
import design
|
||||||
|
design.design.name_map=[]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# instantiate a copy of the class to actually run the test
|
||||||
|
if __name__ == "__main__":
|
||||||
|
(OPTS, args) = globals.parse_args()
|
||||||
|
del sys.argv[1:]
|
||||||
|
header(__file__, OPTS.tech_name)
|
||||||
|
unittest.main()
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env python2.7
|
||||||
|
"""
|
||||||
|
Run regresion tests on a parameterized inverter
|
||||||
|
"""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from testutils import header
|
||||||
|
import sys,os
|
||||||
|
sys.path.append(os.path.join(sys.path[0],".."))
|
||||||
|
import globals
|
||||||
|
import debug
|
||||||
|
import verify
|
||||||
|
|
||||||
|
OPTS = globals.OPTS
|
||||||
|
|
||||||
|
#@unittest.skip("SKIPPING 04_pinv_test")
|
||||||
|
|
||||||
|
|
||||||
|
class pinv_test(unittest.TestCase):
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
|
||||||
|
OPTS.check_lvsdrc = False
|
||||||
|
|
||||||
|
import pinv
|
||||||
|
import tech
|
||||||
|
|
||||||
|
debug.info(2, "Checking 2x size inverter")
|
||||||
|
tx = pinv.pinv(size=2)
|
||||||
|
self.local_check(tx)
|
||||||
|
|
||||||
|
OPTS.check_lvsdrc = True
|
||||||
|
globals.end_openram()
|
||||||
|
|
||||||
|
def local_check(self, tx):
|
||||||
|
tempspice = OPTS.openram_temp + "temp.sp"
|
||||||
|
tempgds = OPTS.openram_temp + "temp.gds"
|
||||||
|
|
||||||
|
tx.sp_write(tempspice)
|
||||||
|
tx.gds_write(tempgds)
|
||||||
|
|
||||||
|
self.assertFalse(verify.run_drc(tx.name, tempgds))
|
||||||
|
self.assertFalse(verify.run_lvs(tx.name, tempgds, tempspice))
|
||||||
|
|
||||||
|
os.remove(tempspice)
|
||||||
|
os.remove(tempgds)
|
||||||
|
|
||||||
|
# reset the static duplicate name checker for unit tests
|
||||||
|
import design
|
||||||
|
design.design.name_map=[]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# instantiate a copy of the class to actually run the test
|
||||||
|
if __name__ == "__main__":
|
||||||
|
(OPTS, args) = globals.parse_args()
|
||||||
|
del sys.argv[1:]
|
||||||
|
header(__file__, OPTS.tech_name)
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue