mirror of https://github.com/VLSIDA/OpenRAM.git
723 lines
43 KiB
Python
723 lines
43 KiB
Python
import contact
|
|
import pgate
|
|
import design
|
|
import debug
|
|
from tech import drc, parameter, spice
|
|
from vector import vector
|
|
from ptx import ptx
|
|
from globals import OPTS
|
|
|
|
class pbitcell(pgate.pgate):
|
|
"""
|
|
This module implements a parametrically sized multi-port bitcell
|
|
"""
|
|
|
|
def __init__(self, num_write=1, num_read=1):
|
|
name = "pbitcell_{0}W_{1}R".format(num_write, num_read)
|
|
pgate.pgate.__init__(self, name)
|
|
debug.info(2, "create a multi-port bitcell with {0} write ports and {1} read ports".format(num_write, num_read))
|
|
|
|
self.num_write = num_write
|
|
self.num_read = num_read
|
|
|
|
self.add_pins()
|
|
self.create_layout()
|
|
self.DRC_LVS()
|
|
|
|
|
|
def add_pins(self):
|
|
for k in range(0,self.num_write):
|
|
self.add_pin("wrow{}".format(k))
|
|
for k in range(0,self.num_write):
|
|
self.add_pin("wbl{}".format(k))
|
|
self.add_pin("wbl_bar{}".format(k))
|
|
if(self.num_read > 0):
|
|
for k in range(0,self.num_read):
|
|
self.add_pin("rrow{}".format(k))
|
|
for k in range(0,self.num_read):
|
|
self.add_pin("rbl{}".format(k))
|
|
self.add_pin("rbl_bar{}".format(k))
|
|
self.add_pin("vdd")
|
|
self.add_pin("gnd")
|
|
|
|
|
|
def create_layout(self):
|
|
self.create_ptx()
|
|
self.add_globals()
|
|
self.add_storage()
|
|
self.add_rails()
|
|
self.add_write_ports()
|
|
if(self.num_read > 0):
|
|
self.add_read_ports()
|
|
self.extend_well()
|
|
self.offset_all_coordinates()
|
|
#self.add_fail()
|
|
|
|
def create_ptx(self):
|
|
""" Calculate transistor sizes """
|
|
# if there are no read ports then write transistors are being used as read/write ports, like in a 6T cell
|
|
if(self.num_read == 0):
|
|
inverter_nmos_width = 3*parameter["min_tx_size"]
|
|
inverter_pmos_width = parameter["min_tx_size"]
|
|
write_nmos_width = 1.5*parameter["min_tx_size"]
|
|
read_nmos_width = parameter["min_tx_size"] # read transistor not necessary but included as to not generate errors in the code when referenced
|
|
|
|
# used for the dual port case where there are separate write and read ports
|
|
else:
|
|
inverter_nmos_width = 2*parameter["min_tx_size"]
|
|
inverter_pmos_width = parameter["min_tx_size"]
|
|
write_nmos_width = parameter["min_tx_size"]
|
|
read_nmos_width = 2*parameter["min_tx_size"]
|
|
|
|
""" Create ptx for all transistors """
|
|
# create ptx for inverter transistors
|
|
self.inverter_nmos = ptx(width=inverter_nmos_width,
|
|
tx_type="nmos")
|
|
self.add_mod(self.inverter_nmos)
|
|
|
|
self.inverter_pmos = ptx(width=inverter_pmos_width,
|
|
tx_type="pmos")
|
|
self.add_mod(self.inverter_pmos)
|
|
|
|
# create ptx for write transitors
|
|
self.write_nmos = ptx(width=write_nmos_width,
|
|
tx_type="nmos")
|
|
self.add_mod(self.write_nmos)
|
|
|
|
# create ptx for read transistors
|
|
self.read_nmos = ptx(width=read_nmos_width,
|
|
tx_type="nmos")
|
|
self.add_mod(self.read_nmos)
|
|
|
|
|
|
def add_globals(self):
|
|
""" Define pbitcell global variables """
|
|
# calculate metal contact extensions over transistor active
|
|
self.inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
|
|
self.write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
|
|
|
|
# calculation for transistor spacing (exact solutions)
|
|
#self.inverter_to_inverter_spacing = 3*parameter["min_tx_size"]
|
|
#self.inverter_to_write_spacing = need to calculate
|
|
#self.write_to_write_spacing = drc["minwidth_metal2"] + self.write_nmos_contact_extension + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"]
|
|
#self.write_to_read_spacing = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.write_nmos_contact_extension
|
|
#self.read_to_read_spacing = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width
|
|
|
|
# calculation for transistor spacing (symmetric solutions)
|
|
self.inverter_to_inverter_spacing = 3*parameter["min_tx_size"]
|
|
#self.inverter_to_write_spacing = need to calculate
|
|
spacing_option1 = contact.poly.width + 2*drc["poly_to_field_poly"] + 2*drc["poly_extend_active"]
|
|
spacing_option2 = contact.poly.width + 2*drc["minwidth_metal2"] + 2*self.write_nmos_contact_extension
|
|
self.write_to_write_spacing = max(spacing_option1, spacing_option2)
|
|
self.write_to_read_spacing = drc["poly_to_field_poly"] + 2*contact.poly.width + 2*drc["minwidth_metal2"] + 2*self.write_nmos_contact_extension
|
|
self.read_to_read_spacing = drc["minwidth_metal1"] + 2*contact.poly.width + 2*drc["minwidth_poly"]
|
|
|
|
# calculations for transistor tiling (includes transistor and spacing)
|
|
self.inverter_tile_width = self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing
|
|
self.write_tile_width = self.write_to_write_spacing + self.write_nmos.active_height
|
|
self.read_tile_width = self.read_to_read_spacing + self.read_nmos.active_height
|
|
|
|
# calculation for row line tiling
|
|
self.rowline_tile_height = drc["minwidth_metal1"] + contact.m1m2.width
|
|
|
|
# calculations related to inverter connections
|
|
self.inverter_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.inverter_pmos_contact_extension
|
|
self.cross_couple_lower_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width
|
|
self.cross_couple_upper_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + drc["poly_to_field_poly"] + 1.5*contact.poly.width
|
|
|
|
""" Calculations for the edges of the cell """
|
|
# create a flag for excluding read port calculations if they are not included in the bitcell
|
|
if(self.num_read > 0):
|
|
read_port_flag = 1
|
|
else:
|
|
read_port_flag = 0
|
|
|
|
# leftmost position = storage width + write ports width + read ports width + read transistor gate connections + metal spacing necessary for tiling the bitcell
|
|
self.leftmost_xpos = -self.inverter_tile_width \
|
|
- self.num_write*self.write_tile_width \
|
|
- read_port_flag*(self.write_to_read_spacing + self.read_nmos.active_height + (self.num_read-1)*self.read_tile_width) \
|
|
- drc["minwidth_poly"] - contact.m1m2.height \
|
|
- 0.5*drc["minwidth_metal2"]
|
|
|
|
self.rightmost_xpos = -self.leftmost_xpos
|
|
|
|
# bottommost position = gnd height + wrow height + rrow height
|
|
self.botmost_ypos = -self.rowline_tile_height \
|
|
- self.num_write*self.rowline_tile_height \
|
|
- read_port_flag*(self.num_read*self.rowline_tile_height)
|
|
|
|
# topmost position = height of the inverter + height of vdd
|
|
self.topmost_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \
|
|
+ self.inverter_pmos_contact_extension + 2*drc["minwidth_metal1"]
|
|
|
|
# calculations for the cell dimensions
|
|
self.width = -2*self.leftmost_xpos
|
|
self.height = self.topmost_ypos - self.botmost_ypos
|
|
|
|
|
|
def add_storage(self):
|
|
"""
|
|
Creates the crossed coupled inverters that act as storage for the bitcell.
|
|
The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar".
|
|
"""
|
|
|
|
# calculate transistor offsets
|
|
left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width
|
|
right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing
|
|
inverter_pmos_ypos = self.inverter_nmos.active_height + self.inverter_gap
|
|
|
|
# create active for nmos
|
|
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
|
|
mod=self.inverter_nmos,
|
|
offset=[left_inverter_xpos,0])
|
|
self.connect_inst(["Q_bar", "Q", "gnd", "gnd"])
|
|
|
|
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
|
|
mod=self.inverter_nmos,
|
|
offset=[right_inverter_xpos,0])
|
|
self.connect_inst(["gnd", "Q_bar", "Q", "gnd"])
|
|
|
|
# create active for pmos
|
|
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
|
|
mod=self.inverter_pmos,
|
|
offset=[left_inverter_xpos, inverter_pmos_ypos])
|
|
self.connect_inst(["Q_bar", "Q", "vdd", "vdd"])
|
|
|
|
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
|
|
mod=self.inverter_pmos,
|
|
offset=[right_inverter_xpos, inverter_pmos_ypos])
|
|
self.connect_inst(["vdd", "Q_bar", "Q", "vdd"])
|
|
|
|
# connect input (gate) of inverters
|
|
self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()])
|
|
self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()])
|
|
|
|
# connect output (drain/source) of inverters
|
|
self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width)
|
|
self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_width)
|
|
|
|
# add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar)
|
|
contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, self.cross_couple_upper_ypos)
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=contact_offset_left,
|
|
rotate=90)
|
|
|
|
contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos)
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=contact_offset_right,
|
|
rotate=90)
|
|
|
|
# connect contacts to gate poly (cross couple)
|
|
gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y)
|
|
self.add_path("poly", [contact_offset_left, gate_offset_right])
|
|
|
|
gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y)
|
|
self.add_path("poly", [contact_offset_right, gate_offset_left])
|
|
|
|
|
|
def add_rails(self):
|
|
"""
|
|
Add gnd and vdd rails and connects them to the inverters
|
|
"""
|
|
|
|
""" Add rails for vdd and gnd """
|
|
self.gnd_position = vector(self.leftmost_xpos, -self.rowline_tile_height)
|
|
self.gnd = self.add_layout_pin(text="gnd",
|
|
layer="metal1",
|
|
offset=self.gnd_position,
|
|
width=self.width,
|
|
height=contact.well.second_layer_width)
|
|
|
|
self.vdd_position = vector(self.leftmost_xpos, self.inverter_pmos_left.get_pin("S").uc().y + drc["minwidth_metal1"])
|
|
self.vdd = self.add_layout_pin(text="vdd",
|
|
layer="metal1",
|
|
offset=self.vdd_position,
|
|
width=self.width,
|
|
height=drc["minwidth_metal1"])
|
|
|
|
""" Connect inverters to rails """
|
|
# connect inverter nmos to gnd
|
|
gnd_pos_left = vector(self.inverter_nmos_left.get_pin("S").bc().x, self.gnd_position.y)
|
|
self.add_path("metal1", [self.inverter_nmos_left.get_pin("S").bc(), gnd_pos_left])
|
|
|
|
gnd_pos_right = vector(self.inverter_nmos_right.get_pin("D").bc().x, self.gnd_position.y)
|
|
self.add_path("metal1", [self.inverter_nmos_right.get_pin("D").bc(), gnd_pos_right])
|
|
|
|
# connect inverter pmos to vdd
|
|
vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y)
|
|
self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left])
|
|
|
|
vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y)
|
|
self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right])
|
|
|
|
|
|
def add_write_ports(self):
|
|
"""
|
|
Adds write ports to the bit cell. A single transistor acts as the write port.
|
|
A write is enabled by setting a Write-Rowline (WROW) high, subsequently turning on the transistor.
|
|
The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q).
|
|
Driving WBL high or low sets the value of the cell.
|
|
This is a differential design, so each write port has a mirrored port that connects WBL_bar to Q_bar.
|
|
"""
|
|
|
|
""" Define variables relevant to write transistors """
|
|
# define offset correction due to rotation of the ptx cell
|
|
write_rotation_correct = self.write_nmos.active_height
|
|
|
|
# define write transistor variables as empty arrays based on the number of write ports
|
|
self.write_nmos_left = [None] * self.num_write
|
|
self.write_nmos_right = [None] * self.num_write
|
|
self.wrow_positions = [None] * self.num_write
|
|
self.wbl_positions = [None] * self.num_write
|
|
self.wbl_bar_positions = [None] * self.num_write
|
|
|
|
# iterate over the number of write ports
|
|
for k in range(0,self.num_write):
|
|
""" Add transistors """
|
|
# calculate write transistor offsets
|
|
left_write_transistor_xpos = -self.inverter_tile_width \
|
|
- (k+1)*self.write_tile_width \
|
|
+ write_rotation_correct
|
|
|
|
right_write_transistor_xpos = self.inverter_tile_width \
|
|
+ self.write_to_write_spacing + k*self.write_tile_width \
|
|
+ write_rotation_correct
|
|
|
|
# add write transistors
|
|
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
|
|
mod=self.write_nmos,
|
|
offset=[left_write_transistor_xpos,0],
|
|
rotate=90)
|
|
self.connect_inst(["Q", "wrow{}".format(k), "wbl{}".format(k), "gnd"])
|
|
|
|
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
|
|
mod=self.write_nmos,
|
|
offset=[right_write_transistor_xpos,0],
|
|
rotate=90)
|
|
self.connect_inst(["Q_bar", "wrow{}".format(k), "wbl_bar{}".format(k), "gnd"])
|
|
|
|
""" Add WROW lines """
|
|
# calculate WROW position
|
|
wrow_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height
|
|
self.wrow_positions[k] = vector(self.leftmost_xpos, wrow_ypos)
|
|
|
|
# add pin for WROW
|
|
self.add_layout_pin(text="wrow{}".format(k),
|
|
layer="metal1",
|
|
offset=self.wrow_positions[k],
|
|
width=self.width,
|
|
height=contact.m1m2.width)
|
|
|
|
""" Source/WBL/WBL_bar connections """
|
|
# add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar
|
|
offset_left = self.write_nmos_left[k].get_pin("S").center()
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=offset_left,
|
|
rotate=90)
|
|
|
|
offset_right = self.write_nmos_right[k].get_pin("S").center()
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=offset_right,
|
|
rotate=90)
|
|
|
|
# add pins for WBL and WBL_bar, overlaid on source contacts
|
|
self.wbl_positions[k] = vector(self.write_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
|
self.add_layout_pin(text="wbl{}".format(k),
|
|
layer="metal2",
|
|
offset=self.wbl_positions[k],
|
|
width=drc["minwidth_metal2"],
|
|
height=self.height)
|
|
|
|
self.wbl_bar_positions[k] = vector(self.write_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
|
self.add_layout_pin(text="wbl_bar{}".format(k),
|
|
layer="metal2",
|
|
offset=self.wbl_bar_positions[k],
|
|
width=drc["minwidth_metal2"],
|
|
height=self.height)
|
|
|
|
""" Gate/WROW connections """
|
|
# add poly-to-meltal2 contacts to connect gate of write transistors to WROW (contact next to gate)
|
|
# contact must be placed a metal width below the source pin to avoid drc from routing to the source pins
|
|
contact_xpos = self.write_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width
|
|
contact_ypos = self.write_nmos_left[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height
|
|
left_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=left_gate_contact)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_gate_contact)
|
|
|
|
contact_xpos = self.write_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width
|
|
contact_ypos = self.write_nmos_right[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height
|
|
right_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=right_gate_contact)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=right_gate_contact)
|
|
|
|
# connect gate of write transistor to contact (poly path)
|
|
midL = vector(left_gate_contact.x, self.write_nmos_left[k].get_pin("G").lc().y)
|
|
self.add_path("poly", [self.write_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width)
|
|
|
|
midR = vector(right_gate_contact.x, self.write_nmos_right[k].get_pin("G").rc().y)
|
|
self.add_path("poly", [self.write_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width)
|
|
|
|
# add metal1-to-metal2 contacts to WROW lines
|
|
left_wrow_contact = vector(left_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_wrow_contact,
|
|
rotate=90)
|
|
|
|
right_wrow_contact = vector(right_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=right_wrow_contact,
|
|
rotate=90)
|
|
|
|
# connect write transistor gate contacts to WROW contacts (metal2 path)
|
|
self.add_path("metal2", [left_gate_contact, left_wrow_contact])
|
|
self.add_path("metal2", [right_gate_contact, right_wrow_contact])
|
|
|
|
""" Drain/Storage connections """
|
|
# this path only needs to be drawn once on the last iteration of the loop
|
|
if(k == self.num_write-1):
|
|
# add contacts to connect gate of inverters to drain of write transistors
|
|
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos)
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=left_storage_contact,
|
|
rotate=90)
|
|
|
|
right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos)
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=right_storage_contact,
|
|
rotate=90)
|
|
|
|
# connect gate of inverters to contacts (poly path)
|
|
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos)
|
|
self.add_path("poly", [left_storage_contact, inverter_gate_offset_left])
|
|
|
|
inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos)
|
|
self.add_path("poly", [right_storage_contact, inverter_gate_offset_right])
|
|
|
|
# connect contacts to drains of write transistors (metal1 path)
|
|
midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y)
|
|
midL1 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], self.write_nmos_left[k].get_pin("D").lc().y)
|
|
self.add_path("metal1", [left_storage_contact, midL0, midL1, self.write_nmos_left[k].get_pin("D").lc()])
|
|
|
|
midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y)
|
|
midR1 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], self.write_nmos_right[k].get_pin("D").rc().y)
|
|
self.add_path("metal1", [right_storage_contact, midR0, midR1, self.write_nmos_right[k].get_pin("D").rc()])
|
|
|
|
|
|
def add_read_ports(self):
|
|
"""
|
|
Adds read ports to the bit cell. Two transistors function as a read port.
|
|
The two transistors in the port are denoted as the "read transistor" and the "read-access transistor".
|
|
The read transistor is connected to RROW (gate), RBL (drain), and the read-access transistor (source).
|
|
The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain).
|
|
A read is enabled by setting a Read-Rowline (RROW) high, subsequently turning on the read transistor.
|
|
The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor
|
|
is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read
|
|
using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q.
|
|
"""
|
|
|
|
""" Define variables relevant to read transistors """
|
|
# define offset correction due to rotation of the ptx cell
|
|
read_rotation_correct = self.read_nmos.active_height
|
|
|
|
# calculate offset to overlap the drain of the read-access transistor with the source of the read transistor
|
|
overlap_offset = self.read_nmos.get_pin("D").ll() - self.read_nmos.get_pin("S").ll()
|
|
|
|
# define read transistor variables as empty arrays based on the number of read ports
|
|
self.read_nmos_left = [None] * self.num_read
|
|
self.read_nmos_right = [None] * self.num_read
|
|
self.read_access_nmos_left = [None] * self.num_read
|
|
self.read_access_nmos_right = [None] * self.num_read
|
|
self.rrow_positions = [None] * self.num_read
|
|
self.rbl_positions = [None] * self.num_read
|
|
self.rbl_bar_positions = [None] * self.num_read
|
|
|
|
# iterate over the number of read ports
|
|
for k in range(0,self.num_read):
|
|
""" Add transistors """
|
|
# calculate transistor offsets
|
|
left_read_transistor_xpos = -self.inverter_tile_width \
|
|
- self.num_write*self.write_tile_width \
|
|
- self.write_to_read_spacing - self.read_nmos.active_height - k*self.read_tile_width \
|
|
+ read_rotation_correct
|
|
|
|
right_read_transistor_xpos = self.inverter_tile_width \
|
|
+ self.num_write*self.write_tile_width \
|
|
+ self.write_to_read_spacing + k*self.read_tile_width \
|
|
+ read_rotation_correct
|
|
|
|
# add read-access transistors
|
|
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left",
|
|
mod=self.read_nmos,
|
|
offset=[left_read_transistor_xpos,0],
|
|
rotate=90)
|
|
self.connect_inst(["RA_to_R_left{}".format(k), " Q_bar", "gnd", "gnd"])
|
|
|
|
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right",
|
|
mod=self.read_nmos,
|
|
offset=[right_read_transistor_xpos,0],
|
|
rotate=90)
|
|
self.connect_inst(["RA_to_R_right{}".format(k), "Q", "gnd", "gnd"])
|
|
|
|
# add read transistors
|
|
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left",
|
|
mod=self.read_nmos,
|
|
offset=[left_read_transistor_xpos,overlap_offset.x],
|
|
rotate=90)
|
|
self.connect_inst(["rbl{}".format(k), "rrow{}".format(k), "RA_to_R_left{}".format(k), "gnd"])
|
|
|
|
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right",
|
|
mod=self.read_nmos,
|
|
offset=[right_read_transistor_xpos,overlap_offset.x],
|
|
rotate=90)
|
|
self.connect_inst(["rbl_bar{}".format(k), "rrow{}".format(k), "RA_to_R_right{}".format(k), "gnd"])
|
|
|
|
""" Add RROW lines """
|
|
# calculate RROW position
|
|
rrow_ypos = self.gnd_position.y \
|
|
- self.num_write*self.rowline_tile_height \
|
|
- (k+1)*self.rowline_tile_height
|
|
self.rrow_positions[k] = vector(self.leftmost_xpos, rrow_ypos)
|
|
|
|
# add pin for RROW
|
|
self.add_layout_pin(text="rrow{}".format(k),
|
|
layer="metal1",
|
|
offset=self.rrow_positions[k],
|
|
width=self.width,
|
|
height=contact.m1m2.width)
|
|
|
|
""" Drain of read transistor / RBL & RBL_bar connection """
|
|
# add metal1-to-metal2 contacts on top of read transistor drain pins for connection to RBL and RBL_bar
|
|
offset_left = self.read_nmos_left[k].get_pin("D").center()
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=offset_left,
|
|
rotate=90)
|
|
|
|
offset_right = self.read_nmos_right[k].get_pin("D").center()
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=offset_right,
|
|
rotate=90)
|
|
|
|
# add pins for RBL and RBL_bar, overlaid on drain contacts
|
|
self.rbl_positions[k] = vector(self.read_nmos_left[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
|
self.add_layout_pin(text="rbl{}".format(k),
|
|
layer="metal2",
|
|
offset=self.rbl_positions[k],
|
|
width=drc["minwidth_metal2"],
|
|
height=self.height)
|
|
|
|
self.rbl_bar_positions[k] = vector(self.read_nmos_right[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
|
self.add_layout_pin(text="rbl_bar{}".format(k),
|
|
layer="metal2",
|
|
offset=self.rbl_bar_positions[k],
|
|
width=drc["minwidth_metal2"],
|
|
height=self.height)
|
|
|
|
""" Gate of read transistor / RROW connection """
|
|
# add poly-to-meltal2 contacts to connect gate of read transistors to RROW (contact next to gate)
|
|
contact_xpos = left_read_transistor_xpos - self.read_nmos.active_height - drc["minwidth_poly"] - 0.5*contact.poly.width
|
|
contact_ypos = self.read_nmos_left[k].get_pin("G").lc().y
|
|
left_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=left_gate_contact)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_gate_contact)
|
|
|
|
contact_xpos = right_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width
|
|
contact_ypos = self.read_nmos_right[k].get_pin("G").rc().y
|
|
right_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=right_gate_contact)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=right_gate_contact)
|
|
|
|
# connect gate of read transistor to contact (poly path)
|
|
self.add_path("poly", [self.read_nmos_left[k].get_pin("G").lc(), left_gate_contact])
|
|
self.add_path("poly", [self.read_nmos_right[k].get_pin("G").rc(), right_gate_contact])
|
|
|
|
# add metal1-to-metal2 contacts to RROW lines
|
|
left_rrow_contact = vector(left_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=left_rrow_contact,
|
|
rotate=90)
|
|
|
|
right_rrow_contact = vector(right_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width)
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
offset=right_rrow_contact,
|
|
rotate=90)
|
|
|
|
# connect read transistor gate contacts to RROW contacts (metal2 path)
|
|
self.add_path("metal2", [left_gate_contact, left_rrow_contact])
|
|
self.add_path("metal2", [right_gate_contact, right_rrow_contact])
|
|
|
|
""" Source of read-access transistor / GND connection """
|
|
# connect source of read-access transistor to GND (metal1 path)
|
|
gnd_offset_left = vector(self.read_access_nmos_left[k].get_pin("S").bc().x, self.gnd_position.y)
|
|
self.add_path("metal1", [self.read_access_nmos_left[k].get_pin("S").bc(), gnd_offset_left])
|
|
|
|
gnd_offset_right = vector(self.read_access_nmos_right[k].get_pin("S").bc().x, self.gnd_position.y)
|
|
self.add_path("metal1", [self.read_access_nmos_right[k].get_pin("S").bc(), gnd_offset_right])
|
|
|
|
""" Gate of read-access transistor / storage connection """
|
|
# add poly-to-metal1 contacts to connect gate of read-access transistors to output of inverters (contact next to gate)
|
|
contact_xpos = left_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width
|
|
contact_ypos = self.read_access_nmos_left[k].get_pin("G").rc().y
|
|
left_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=left_gate_contact)
|
|
|
|
contact_xpos = right_read_transistor_xpos - self.read_nmos.active_height - drc["minwidth_poly"] - 0.5*contact.poly.width
|
|
contact_ypos = self.read_access_nmos_right[k].get_pin("G").lc().y
|
|
right_gate_contact = vector(contact_xpos, contact_ypos)
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
offset=right_gate_contact)
|
|
|
|
# connect gate of read-access transistor to contact (poly path)
|
|
self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").rc(), left_gate_contact])
|
|
self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").lc(), right_gate_contact])
|
|
|
|
# connect contact to output of inverters (metal1 path)
|
|
# mid0: metal1 path must route over the read transistors (above drain of read transistor)
|
|
# mid1: continue metal1 path horizontally until at inverter
|
|
# mid2: route down to be level with inverter output
|
|
# endpoint at drain/source of inverter
|
|
midL0 = vector(left_gate_contact.x, self.read_nmos_left[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.read_nmos_left[0].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
midL2 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos)
|
|
left_inverter_offset = vector(self.inverter_nmos_left.get_pin("D").center().x, self.cross_couple_upper_ypos)
|
|
self.add_path("metal1", [left_gate_contact, midL0, midL1, midL2, left_inverter_offset])
|
|
|
|
midR0 = vector(right_gate_contact.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
midR2 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos)
|
|
right_inverter_offset = vector(self.inverter_nmos_right.get_pin("S").center().x, self.cross_couple_upper_ypos)
|
|
self.add_path("metal1", [right_gate_contact, midR0, midR1, midR2, right_inverter_offset])
|
|
|
|
|
|
def extend_well(self):
|
|
"""
|
|
Connects wells between ptx cells to avoid drc spacing issues.
|
|
Since the pwell of the read ports rise higher than the pmos of the inverters,
|
|
the well connections must be done piecewise to avoid pwell and nwell overlap.
|
|
"""
|
|
|
|
""" extend pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well """
|
|
offset = vector(self.leftmost_xpos, self.botmost_ypos)
|
|
well_height = -self.botmost_ypos + self.inverter_nmos.cell_well_height - drc["well_enclosure_active"]
|
|
self.add_rect(layer="pwell",
|
|
offset=offset,
|
|
width=self.width,
|
|
height=well_height)
|
|
|
|
""" extend pwell over write transistors to the height of the write transistor well """
|
|
# calculate the edge of the write transistor well closest to the center
|
|
left_write_well_xpos = -(self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"])
|
|
right_write_well_xpos = self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"]
|
|
|
|
# calculate a width that will halt at the edge of the write transistors
|
|
write_well_width = -(self.leftmost_xpos - left_write_well_xpos)
|
|
write_well_height = self.write_nmos.cell_well_width - drc["well_enclosure_active"]
|
|
|
|
offset = vector(left_write_well_xpos - write_well_width, 0)
|
|
self.add_rect(layer="pwell",
|
|
offset=offset,
|
|
width=write_well_width,
|
|
height=write_well_height)
|
|
|
|
offset = vector(right_write_well_xpos, 0)
|
|
self.add_rect(layer="pwell",
|
|
offset=offset,
|
|
width=write_well_width,
|
|
height=write_well_height)
|
|
|
|
""" extend pwell over the read transistors to the height of the bitcell """
|
|
if(self.num_read > 0):
|
|
# calculate the edge of the read transistor well clostest to the center
|
|
left_read_well_xpos = -(self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"])
|
|
right_read_well_xpos = self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"]
|
|
|
|
# calculate a width that will halt at the edge of the read transistors
|
|
read_well_width = -(self.leftmost_xpos - left_read_well_xpos)
|
|
read_well_height = self.topmost_ypos
|
|
|
|
offset = vector(self.leftmost_xpos, 0)
|
|
self.add_rect(layer="pwell",
|
|
offset=offset,
|
|
width=read_well_width,
|
|
height=read_well_height)
|
|
|
|
offset = vector(right_read_well_xpos, 0)
|
|
self.add_rect(layer="pwell",
|
|
offset=offset,
|
|
width=read_well_width,
|
|
height=read_well_height)
|
|
|
|
""" extend nwell to encompass inverter_pmos """
|
|
# calculate offset of the left pmos well
|
|
inverter_well_xpos = -self.inverter_tile_width - drc["well_enclosure_active"]
|
|
inverter_well_ypos = self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"]
|
|
|
|
# calculate width of the two combined nwells
|
|
# calculate height to encompass nimplant connected to vdd
|
|
well_width = 2*self.inverter_tile_width + 2*drc["well_enclosure_active"]
|
|
well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
|
|
|
|
offset = [inverter_well_xpos,inverter_well_ypos]
|
|
self.add_rect(layer="nwell",
|
|
offset=offset,
|
|
width=well_width,
|
|
height=well_height)
|
|
|
|
|
|
""" add well contacts """
|
|
# connect pimplants to gnd
|
|
offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width)
|
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
|
offset=offset,
|
|
rotate=90)
|
|
|
|
self.add_rect_center(layer="pimplant",
|
|
offset=offset,
|
|
width=drc["minwidth_tx"],
|
|
height=drc["minwidth_tx"])
|
|
|
|
# connect nimplants to vdd
|
|
offset = vector(0, self.vdd_position.y + 0.5*drc["minwidth_metal1"])
|
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
|
offset=offset,
|
|
rotate=90)
|
|
|
|
self.add_rect_center(layer="nimplant",
|
|
offset=offset,
|
|
width=drc["minwidth_tx"],
|
|
height=drc["minwidth_tx"])
|
|
|
|
|
|
def add_fail(self):
|
|
# for failing drc when I want to observe the gds layout
|
|
frail_width = self.well_width = 2*drc["minwidth_metal1"]
|
|
frail_height = self.rail_height = drc["minwidth_metal1"]
|
|
|
|
fail_position = vector(-25*drc["minwidth_tx"], - 1.5 * drc["minwidth_metal1"] - 0.5 * frail_height) # for tiling purposes
|
|
self.add_layout_pin(text="gnd",
|
|
layer="metal1",
|
|
offset=fail_position,
|
|
width=frail_width,
|
|
height=frail_height)
|
|
|
|
fail_position2 = vector(-25*drc["minwidth_tx"], - 0.5 * drc["minwidth_metal1"])
|
|
self.add_layout_pin(text="gnd2",
|
|
layer="metal1",
|
|
offset=fail_position2,
|
|
width=frail_width,
|
|
height=frail_height)
|