OpenRAM/compiler/nand_2.py

445 lines
19 KiB
Python

import contact
import design
import debug
from tech import drc, cell
from ptx import ptx
from vector import vector
from globals import OPTS
class nand_2(design.design):
"""
This module generates gds of a parametrically sized 2_input nand.
This model use ptx to generate a 2_input nand within a cetrain height.
The 2_input nand's cell_height should be the same as the 6t library cell
This module doesn't generate multi_finger 2_input nand gate,
It generate only the minimum size 2_input nand gate.
Creates a pcell for a simple 2_input nand
"""
c = reload(__import__(OPTS.config.bitcell))
bitcell = getattr(c, OPTS.config.bitcell)
def __init__(self, name, nmos_width=1, height=bitcell.chars["height"]):
""" Constructor """
design.design.__init__(self, name)
debug.info(2, "create nand_2 strcuture {0} with size of {1}".format(
name, nmos_width))
self.nmos_width = nmos_width
self.height = height
self.add_pins()
self.create_layout()
self.DRC_LVS()
def add_pins(self):
""" Add pins """
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
def create_layout(self):
""" Layout """
self.determine_sizes()
self.create_ptx()
self.setup_layout_constants()
self.add_rails()
self.add_ptx()
self.add_well_contacts()
# This isn't instantiated, but we use it to get the dimensions
self.poly_contact = contact.contact(("poly", "contact", "metal1"))
self.connect_well_contacts()
self.connect_rails()
self.connect_tx()
self.route_pins()
self.extend_wells()
self.extend_active()
self.setup_layout_offsets()
# Determine transistor size
def determine_sizes(self):
""" Determine the size of the transistors """
self.nmos_size = self.nmos_width
self.pmos_size = self.nmos_width
self.tx_mults = 1
# transistors are created here but not yet placed or added as a module
def create_ptx(self):
""" Add required modules """
self.nmos1 = ptx(name="nand_2_nmos1",
width=self.nmos_size,
mults=self.tx_mults,
tx_type="nmos")
self.add_mod(self.nmos1)
self.nmos2 = ptx(name="nand_2_nmos2",
width=self.nmos_size,
mults=self.tx_mults,
tx_type="nmos")
self.add_mod(self.nmos2)
self.pmos1 = ptx(name="nand_2_pmos1",
width=self.pmos_size,
mults=self.tx_mults,
tx_type="pmos")
self.add_mod(self.pmos1)
self.pmos2 = ptx(name="nand_2_pmos2",
width=self.pmos_size,
mults=self.tx_mults,
tx_type="pmos")
self.add_mod(self.pmos2)
def setup_layout_constants(self):
""" Calculate the layout constraints """
self.well_width = self.pmos1.active_position[0] \
+ 2 * self.pmos1.active_width \
+ drc["active_to_body_active"] + \
drc["well_enclosure_active"]
self.width = self.well_width
def add_rails(self):
""" add power rails """
rail_width = self.width
rail_height = drc["minwidth_metal1"]
self.rail_height = rail_height
# Relocate the origin
self.gnd_position = vector(0, - 0.5 * drc["minwidth_metal1"])
self.add_rect(layer="metal1",
offset=self.gnd_position,
width=rail_width,
height=rail_height)
self.add_label(text="gnd",
layer="metal1",
offset=self.gnd_position)
self.vdd_position = vector(0, self.height - 0.5 * drc["minwidth_metal1"])
self.add_rect(layer="metal1",
offset=self.vdd_position,
width=rail_width,
height=rail_height)
self.add_label(text="vdd",
layer="metal1",
offset=self.vdd_position)
def add_ptx(self):
""" transistors are added and placed inside the layout """
# determines the spacing between the edge and nmos (rail to active
# metal or poly_to_poly spacing)
edge_to_nmos = max(drc["metal1_to_metal1"]
- self.nmos1.active_contact_positions[0][1],
0.5 * (drc["poly_to_poly"] - drc["minwidth_metal1"])
- self.nmos1.poly_positions[0][1])
# determine the position of the first transistor from the left
self.nmos_position1 = vector(0, 0.5 * drc["minwidth_metal1"] + edge_to_nmos)
offset = self.nmos_position1+ vector(0,self.nmos1.height)
self.add_inst(name="nmos1",
mod=self.nmos1,
offset=offset,
mirror="MX")
self.connect_inst(["Z", "A", "net1", "gnd"])
self.nmos_position2 = vector(self.nmos2.active_width - self.nmos2.active_contact.width,
self.nmos_position1[1])
offset = self.nmos_position2 + vector(0,self.nmos2.height)
self.add_inst(name="nmos2",
mod=self.nmos2,
offset=offset,
mirror="MX")
self.connect_inst(["net1", "B", "gnd", "gnd"])
# determines the spacing between the edge and pmos
edge_to_pmos = max(drc["metal1_to_metal1"] \
- self.pmos1.active_contact_positions[0][1],
0.5 * drc["poly_to_poly"] - 0.5 * drc["minwidth_metal1"] \
- self.pmos1.poly_positions[0][1])
self.pmos_position1 = vector(0, self.height - 0.5 * drc["minwidth_metal1"]
- edge_to_pmos - self.pmos1.height)
self.add_inst(name="pmos1",
mod=self.pmos1,
offset=self.pmos_position1)
self.connect_inst(["vdd", "A", "Z", "vdd"])
self.pmos_position2 = vector(self.nmos_position2.x, self.pmos_position1.y)
self.add_inst(name="pmos2",
mod=self.pmos2,
offset=self.pmos_position2)
self.connect_inst(["Z", "B", "vdd", "vdd"])
def add_well_contacts(self):
""" Create and add well copntacts """
# create well contacts
layer_stack = ("active", "contact", "metal1")
xoffset = (self.nmos_position2.x + self.pmos1.active_position.x
+ self.pmos1.active_width + drc["active_to_body_active"])
yoffset = (self.pmos_position1.y +
self.pmos1.active_contact_positions[0].y)
offset = self.nwell_contact_position = vector(xoffset, yoffset)
self.pwell_contact=self.add_contact(layer_stack,offset,(1,self.nmos1.num_of_tacts))
xoffset = (self.nmos_position2.x + self.nmos1.active_position.x
+ self.nmos1.active_width + drc["active_to_body_active"])
yoffset = (self.nmos_position1.y + self.nmos1.height
- self.nmos1.active_contact_positions[0].y
- self.nmos1.active_contact.height)
offset = self.pwell_contact_position = vector(xoffset, yoffset)
self.nwell_contact=self.add_contact(layer_stack,offset,(1,self.pmos1.num_of_tacts))
def connect_well_contacts(self):
""" Connect well contacts to vdd and gnd rail """
well_tap_length = (self.height - self.nwell_contact_position.y)
offset = vector(self.nwell_contact_position.x
+ self.nwell_contact.second_layer_position.x
- self.nwell_contact.first_layer_position.x,
self.nwell_contact_position.y)
self.add_rect(layer="metal1",
offset=offset,
width=drc["minwidth_metal1"],
height=well_tap_length)
well_tap_length = self.nmos1.active_height
offset = (self.pwell_contact_position.scale(1,0)
+ self.pwell_contact.second_layer_position.scale(1,0)
- self.pwell_contact.first_layer_position.scale(1,0))
self.add_rect(layer="metal1",
offset=offset, width=drc["minwidth_metal1"],
height=well_tap_length)
def connect_rails(self):
""" Connect transistor pmos drains to vdd and nmos drains to gnd rail """
correct = vector(self.pmos1.active_contact.width - drc["minwidth_metal1"],
0).scale(.5,0)
poffset = self.pmos_position1 + self.pmos1.active_contact_positions[0] + correct
temp_height = self.height - poffset[1]
self.add_rect(layer="metal1",
offset=poffset, width=drc["minwidth_metal1"],
height=temp_height)
poffset = vector(2 * self.pmos_position2.x + correct.x
+ self.pmos2.active_contact_positions[0].x , poffset[1])
self.add_rect(layer="metal1",
offset=poffset,
width=drc["minwidth_metal1"],
height=temp_height)
poffset = self.nmos_position1 + self.nmos1.active_contact_positions[0] + correct
self.add_rect(layer="metal1",
offset=poffset.scale(1,0),
width=drc["minwidth_metal1"],
height=temp_height)
def connect_tx(self):
""" Connect tx poly znd drains """
self.connect_poly()
self.connect_drains()
def connect_poly(self):
""" poly connection """
yoffset_nmos1 = (self.nmos_position1.y
+ self.nmos1.poly_positions[0].y
+ self.nmos1.poly_height)
poly_length = (self.pmos_position1.y + self.pmos1.poly_positions[0].y
- yoffset_nmos1 + drc["minwidth_poly"])
for position in self.pmos1.poly_positions:
offset = [position[0],
yoffset_nmos1 - 0.5 * drc["minwidth_poly"]]
self.add_rect(layer="poly",
offset=offset, width=drc["minwidth_poly"],
height=poly_length)
self.add_rect(layer="poly",
offset=[offset[0] + self.pmos1.active_contact.width + 2 * drc["minwidth_poly"],
offset[1]],
width=drc["minwidth_poly"],
height=poly_length)
def connect_drains(self):
""" Connect pmos and nmos drains. The output will be routed to this connection point. """
yoffset = self.nmos_position1.y + self.nmos1.active_contact_positions[0].y
drain_length = (self.height + self.pmos1.active_contact_positions[0].y
- yoffset - self.pmos1.height + 0.5 * drc["minwidth_metal2"])
for position in self.pmos1.active_contact_positions[1:][::2]:
start = self.drain_position = [position[0] + 0.5 * drc["minwidth_metal1"]
+ self.pmos_position2[0]
+ self.pmos2.active_contact.first_layer_position[0]
+ self.pmos2.active_contact.width / 2,
yoffset]
mid1 = [start[0],
self.height - drc["minwidth_metal2"] - drc["metal2_to_metal2"] -
self.pmos_size - drc["metal1_to_metal1"] - 0.5 * drc["minwidth_metal1"]]
end = [position[0] + 0.5 * drc["minwidth_metal1"]
+ self.pmos2.active_contact.second_layer_position[0],
self.pmos_position1[1] + self.pmos1.active_contact_positions[0][1]]
mid2 = [end[0], mid1[1]]
self.add_path("metal1",[start, mid1, mid2, end])
def route_pins(self):
""" Routing """
self.route_input_gate()
self.route_output()
def route_input_gate(self):
""" Gate routing """
self.route_input_gate_A()
self.route_input_gate_B()
def route_input_gate_A(self):
""" routing for input A """
xoffset = self.pmos1.poly_positions[0][0]
yoffset = (self.height
- (drc["minwidth_metal1"]
+ drc["metal1_to_metal1"]
+ self.pmos2.active_height
+ drc["metal1_to_metal1"]
+ self.pmos2.active_contact.second_layer_width))
if (self.nmos_width == drc["minwidth_tx"]):
yoffset = (self.pmos_position1[1]
+ self.pmos1.poly_positions[0][1]
+ drc["poly_extend_active"]
- (self.pmos1.active_contact.height
- self.pmos1.active_height) / 2
- drc["metal1_to_metal1"]
- self.poly_contact.width)
offset = [xoffset, yoffset]
self.add_contact(layers=("poly", "contact", "metal1"),
offset=offset,
size=(1,1),
rotate=90)
offset = offset - self.poly_contact.first_layer_position.rotate().scale(1,0)
self.add_rect(layer="poly",
offset=offset,
width=self.poly_contact.first_layer_position[1] + drc["minwidth_poly"],
height=self.poly_contact.first_layer_width)
input_length = (self.pmos1.poly_positions[0][0]
- self.poly_contact.height)
yoffset += self.poly_contact.via_layer_position[0]
offset = self.input_position1 = vector(0, yoffset)
self.add_rect(layer="metal1",
offset=offset,
width=input_length,
height=drc["minwidth_metal1"])
self.add_label(text="A",
layer="metal1",
offset=offset)
def route_input_gate_B(self):
""" routing for input B """
xoffset = (self.pmos2.poly_positions[0][0]
+ self.pmos_position2[0] + drc["minwidth_poly"])
yoffset = (drc["minwidth_metal1"]
+ drc["metal1_to_metal1"]
+ self.nmos2.active_height
+ drc["minwidth_metal1"])
if (self.nmos_width == drc["minwidth_tx"]):
yoffset = (self.nmos_position1[1]
+ self.nmos1.poly_positions[0][1]
+ self.nmos1.poly_height
+ drc["metal1_to_metal1"])
offset = [xoffset, yoffset]
self.add_contact(layers=("poly", "contact", "metal1"),
offset=offset,
size=(1,1),
rotate=90)
input_length = self.pmos2.poly_positions[0].x - self.poly_contact.height
self.input_position2 = vector(xoffset - self.poly_contact.width,
yoffset + self.poly_contact.via_layer_position[0])
self.add_layout_pin(text="B",
layer="metal1",
offset=self.input_position2.scale(0,1),
width=(input_length + self.pmos_position2[0] + drc["minwidth_poly"]),
height=drc["minwidth_metal1"])
def route_output(self):
""" routing for output Z """
yoffset = (self.nmos1.height - 2 * drc["minwidth_metal1"] / 3 +
(self.height - self.pmos1.height - self.nmos1.height - drc["minwidth_metal1"]) / 2 )
xoffset = self.drain_position[0]
offset = self.output_position = vector(xoffset, yoffset)
output_length = self.width - xoffset
self.add_layout_pin(text="Z",
layer="metal1",
offset=offset,
width=output_length,
height=drc["minwidth_metal1"])
def extend_wells(self):
""" Extension of well """
middle_point = (self.nmos_position1.y + self.nmos1.pwell_position.y
+ self.nmos1.well_height
+ (self.pmos_position1.y + self.pmos1.nwell_position.y
- self.nmos_position1.y - self.nmos1.pwell_position.y
- self.nmos1.well_height) / 2)
offset = self.nwell_position = vector(0, middle_point)
self.nwell_height = self.height - middle_point
self.add_rect(layer="nwell",
offset=offset,
width=self.well_width,
height=self.nwell_height)
self.add_rect(layer="vtg",
offset=offset,
width=self.well_width,
height=self.nwell_height)
offset = self.pwell_position = vector(0, 0)
self.pwell_height = middle_point
self.add_rect(layer="pwell",
offset=offset,
width=self.well_width,
height=self.pwell_height)
self.add_rect(layer="vtg",
offset=offset,
width=self.well_width,
height=self.pwell_height)
def extend_active(self):
""" Extension of active region """
self.active_width = (self.pmos1.active_width
+ drc["active_to_body_active"]
+ self.pmos1.active_contact.width)
offset = (self.pmos1.active_position
+ self.pmos_position2.scale(1,0)
+ self.pmos_position1.scale(0,1))
self.add_rect(layer="active",
offset=offset,
width=self.active_width,
height=self.pmos1.active_height)
offset = offset + vector(self.pmos1.active_width, 0)
width = self.active_width - self.pmos1.active_width
self.add_rect(layer="nimplant",
offset=offset,
width=width,
height=self.pmos1.active_height)
offset = vector(self.nmos_position2[0] + self.nmos1.active_position[0],
self.nmos_position1[1] - self.nmos1.active_height
- self.nmos1.active_position[1] + self.nmos1.height)
self.add_rect(layer="active",
offset=offset,
width=self.active_width,
height=self.nmos1.active_height)
offset = offset + vector(self.nmos1.active_width,0)
width = self.active_width - self.nmos1.active_width
self.add_rect(layer="pimplant",
offset=offset,
width=width,
height=self.nmos1.active_height)
def setup_layout_offsets(self):
""" Defining the position of i/o pins for the two input nand gate """
self.A_position = self.A_position = self.input_position1
self.B_position = self.B_position = self.input_position2
self.Z_position = self.Z_position = self.output_position
self.vdd_position = self.vdd_position
self.gnd_position = self.gnd_position