The bitcell currently passes DRC and LVS for FreePDK45 and SCMOS

There are 2 benchtests for the bitcell:
1) one with 2 write ports and 2 read ports
2) one with 2 write ports and 0 read ports
The second test is meant to show how the bitcell functions when read/write ports are
used instead of separate ports for read and write
The bitcell currently passes both tests in both technologies
Certain sizing optimizations still need to be done on the bitcell
This commit is contained in:
Michael Timothy Grimes 2018-02-28 11:14:53 -08:00
parent bf7d846e5f
commit 4d3f01ff2f
4 changed files with 468 additions and 403 deletions

View File

@ -1,104 +1,131 @@
import contact
import pgate
import design import design
import debug import debug
from tech import drc, parameter, spice from tech import drc, parameter, spice
from vector import vector from vector import vector
import contact
from math import ceil
from ptx import ptx from ptx import ptx
from globals import OPTS from globals import OPTS
class pbitcell(design.design): class pbitcell(pgate.pgate):
""" """
This module implements a parametrically sized multi-port bitcell This module implements a parametrically sized multi-port bitcell
""" """
def __init__(self, num_write=1, num_read=1): def __init__(self, num_write=1, num_read=1):
name = "pbitcell" name = "pbitcell_{0}W_{1}R".format(num_write, num_read)
design.design.__init__(self, name) 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)) 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_write = num_write
self.num_read = num_read self.num_read = num_read
self.add_pins()
self.create_layout() self.create_layout()
self.DRC_LVS() self.DRC_LVS()
def add_pins(self): def add_pins(self):
for k in range(0,self.num_write): for k in range(0,self.num_write):
self.add_pin("WROW{}".format(k)) self.add_pin("wrow{}".format(k))
for k in range(0,self.num_write): for k in range(0,self.num_write):
self.add_pin("WBL{}".format(k)) self.add_pin("wbl{}".format(k))
self.add_pin("WBL_bar{}".format(k)) self.add_pin("wbl_bar{}".format(k))
for k in range(0,self.num_read): for k in range(0,self.num_read):
self.add_pin("RROW{}".format(k)) self.add_pin("rrow{}".format(k))
for k in range(0,self.num_read): for k in range(0,self.num_read):
self.add_pin("RBL{}".format(k)) self.add_pin("rbl{}".format(k))
self.add_pin("RBL_bar{}".format(k)) self.add_pin("rbl_bar{}".format(k))
self.add_pin("vdd") self.add_pin("vdd")
self.add_pin("gnd") self.add_pin("gnd")
def create_layout(self): def create_layout(self):
self.create_ptx() self.add_globals()
self.add_storage() self.add_storage()
self.add_rails() self.add_rails()
if(self.num_write > 0): self.add_write_ports()
self.add_write_transistors()
if(self.num_read > 0): if(self.num_read > 0):
self.add_read_transistors() self.add_read_ports()
self.extend_well() self.extend_well()
#self.add_fail() #self.add_fail()
def create_ptx(self): def add_globals(self):
""" Create ptx for all transistors. Also define measurements to be used throughout bitcell """ """ Calculate transistor sizes """
# create ptx for inverter transistors # if there are no read ports then write transistors are being used as read/write ports like in a 6T bitcell
self.i_nmos = ptx(width=2*parameter["min_tx_size"], if(self.num_read == 0):
tx_type="nmos", inverter_nmos_width = 3*parameter["min_tx_size"]
connect_active=True, inverter_pmos_width = parameter["min_tx_size"]
connect_poly=True) write_nmos_width = 1.5*parameter["min_tx_size"]
self.add_mod(self.i_nmos) read_nmos_width = parameter["min_tx_size"]
# 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"]
self.i_pmos = ptx(width=parameter["min_tx_size"], """ Create ptx for all transistors """
tx_type="pmos", # create ptx for inverter transistors
connect_active=True, self.inverter_nmos = ptx(width=inverter_nmos_width,
connect_poly=True) tx_type="nmos")
self.add_mod(self.i_pmos) 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 # create ptx for write transitors
self.w_nmos = ptx(width=parameter["min_tx_size"], self.write_nmos = ptx(width=write_nmos_width,
tx_type="nmos", tx_type="nmos")
connect_active=True, self.add_mod(self.write_nmos)
connect_poly=True)
self.add_mod(self.w_nmos)
# create ptx for read transistors # create ptx for read transistors
self.r_nmos = ptx(width=2*parameter["min_tx_size"], self.read_nmos = ptx(width=read_nmos_width,
mults=2, tx_type="nmos")
tx_type="nmos", self.add_mod(self.read_nmos)
connect_active=False,
connect_poly=False)
self.add_mod(self.r_nmos)
""" Define pbitcell global variables """
# determine metal contact extensions # determine metal contact extensions
self.ip_ex = 0.5*(self.i_pmos.active_contact.height - self.i_pmos.active_height) self.ip_ex = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
self.w_ex = 0.5*(self.w_nmos.active_contact.height - self.w_nmos.active_height) self.w_ex = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
# determine global measurements # calculation for transistor spacing
w_spacer = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] self.write_to_write_spacing = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"]
r_spacer = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width self.read_to_read_spacing = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width
rw_spacer = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex self.write_to_read_spacing = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex
self.w_tile_width = w_spacer + self.w_nmos.active_height # calculations for transistor tiling (includes transistor and spacing)
self.r_tile_width = r_spacer + self.r_nmos.active_height self.inverter_tile_width = self.inverter_nmos.active_width + 1.5*parameter["min_tx_size"]
self.inv_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.ip_ex self.write_tile_width = self.write_to_write_spacing + self.write_nmos.active_height
self.cross_heightL = self.i_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width self.read_tile_width = self.read_to_read_spacing + self.read_nmos.active_height
self.cross_heightU = self.i_nmos.active_height + drc["poly_to_active"] + drc["poly_to_field_poly"] + 1.5*contact.poly.width
self.leftmost_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) \ # calculation for row line tiling
- self.num_write*(w_spacer + self.w_nmos.active_height) \ self.rowline_tile_height = drc["minwidth_metal1"] + contact.m1m2.width
- rw_spacer - self.r_nmos.active_height - (self.num_read-1)*self.r_tile_width \
- 3*drc["minwidth_metal1"] # calculations related to inverter connections
self.botmost_ypos = -2*drc["minwidth_metal1"] - self.num_write*2*drc["minwidth_metal2"] - self.num_read*2*drc["minwidth_metal2"] self.inverter_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.ip_ex
self.topmost_ypos = self.inv_gap + self.i_nmos.active_height + self.i_pmos.active_height + drc["poly_extend_active"] + 2*drc["minwidth_metal1"] 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
if(self.num_read > 0):
read_port_flag = 1;
else:
read_port_flag = 0;
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
self.botmost_ypos = -self.rowline_tile_height \
- self.num_write*self.rowline_tile_height \
- read_port_flag*(self.num_read*self.rowline_tile_height)
self.topmost_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + drc["poly_extend_active"] + 2*drc["minwidth_metal1"]
# calculations for the cell dimensions
self.cell_width = -2*self.leftmost_xpos self.cell_width = -2*self.leftmost_xpos
self.cell_height = self.topmost_ypos - self.botmost_ypos self.cell_height = self.topmost_ypos - self.botmost_ypos
@ -106,69 +133,77 @@ class pbitcell(design.design):
def add_storage(self): def add_storage(self):
""" """
Creates the crossed coupled inverters that act as storage for the bitcell. 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 values as "Q_bar".
""" """
lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"])
rit_xpos = 1.5*parameter["min_tx_size"]
it_ypos = self.inv_gap + self.i_nmos.active_height
# create active # calculate transistor offsets
self.i_nmosL = self.add_inst(name="i_nmosL", left_inverter_xpos = -(self.inverter_nmos.active_width + 1.5*parameter["min_tx_size"])
mod=self.i_nmos, right_inverter_xpos = 1.5*parameter["min_tx_size"]
offset=[lit_xpos,0]) inverter_pmos_ypos = self.inverter_gap + self.inverter_nmos.active_height
# 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.connect_inst(["Q_bar", "Q", "gnd", "gnd"])
self.i_nmosR = self.add_inst(name="i_nmosR", self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
mod=self.i_nmos, mod=self.inverter_nmos,
offset=[rit_xpos,0]) offset=[right_inverter_xpos,0])
self.connect_inst(["gnd", "Q_bar", "Q", "gnd"]) self.connect_inst(["gnd", "Q_bar", "Q", "gnd"])
self.i_pmosL = self.add_inst(name="i_pmosL", # create active for pmos
mod=self.i_pmos, self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
offset=[lit_xpos, it_ypos]) mod=self.inverter_pmos,
offset=[left_inverter_xpos, inverter_pmos_ypos])
self.connect_inst(["Q_bar", "Q", "vdd", "vdd"]) self.connect_inst(["Q_bar", "Q", "vdd", "vdd"])
self.i_pmosR = self.add_inst(name="i_pmosR", self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
mod=self.i_pmos, mod=self.inverter_pmos,
offset=[rit_xpos, it_ypos]) offset=[right_inverter_xpos, inverter_pmos_ypos])
self.connect_inst(["vdd", "Q_bar", "Q", "vdd"]) self.connect_inst(["vdd", "Q_bar", "Q", "vdd"])
# connect poly for inverters # connect input (gate) of inverters
self.add_path("poly", [self.i_nmosL.get_pin("G").uc(), self.i_pmosL.get_pin("G").bc()]) 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.i_nmosR.get_pin("G").uc(), self.i_pmosR.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 drains for inverters # connect output (drain/source) of inverters
self.add_path("metal1", [self.i_nmosL.get_pin("D").uc(), self.i_pmosL.get_pin("D").bc()]) self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.width)
self.add_path("metal1", [self.i_nmosR.get_pin("S").uc(), self.i_pmosR.get_pin("S").bc()]) self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.width)
# add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar) # add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar)
offsetL = vector(self.i_nmosL.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_heightU) offsetL = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.width, self.cross_couple_upper_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetL, offset=offsetL,
rotate=90) rotate=90)
offsetR = vector(self.i_nmosR.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_heightL) offsetR = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.width, self.cross_couple_lower_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetR, offset=offsetR,
rotate=90) rotate=90)
# connect contacts to gate poly (cross couple) # connect contacts to gate poly (cross couple)
gate_offsetR = vector(self.i_nmosR.get_pin("G").lc().x, offsetL.y) gate_offsetR = vector(self.inverter_nmos_right.get_pin("G").lc().x, offsetL.y)
self.add_path("poly", [offsetL, gate_offsetR]) self.add_path("poly", [offsetL, gate_offsetR])
gate_offsetL = vector(self.i_nmosL.get_pin("G").rc().x, offsetR.y) gate_offsetL = vector(self.inverter_nmos_left.get_pin("G").rc().x, offsetR.y)
self.add_path("poly", [offsetR, gate_offsetL]) self.add_path("poly", [offsetR, gate_offsetL])
def add_rails(self): def add_rails(self):
"""
Adds gnd and vdd rails and connects them to the storage element
"""
""" Add rails for vdd and gnd """ """ Add rails for vdd and gnd """
self.gnd_position = vector(self.leftmost_xpos, -2*drc["minwidth_metal1"]) self.gnd_position = vector(self.leftmost_xpos, -self.rowline_tile_height)
self.gnd = self.add_layout_pin(text="gnd", self.gnd = self.add_layout_pin(text="gnd",
layer="metal1", layer="metal1",
offset=self.gnd_position, offset=self.gnd_position,
width=self.cell_width, width=self.cell_width,
height=drc["minwidth_metal1"]) height=contact.well.second_layer_width)
self.vdd_position = vector(self.leftmost_xpos, self.i_pmosL.get_pin("S").uc().y + drc["minwidth_metal1"]) 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", self.vdd = self.add_layout_pin(text="vdd",
layer="metal1", layer="metal1",
offset=self.vdd_position, offset=self.vdd_position,
@ -177,368 +212,432 @@ class pbitcell(design.design):
""" Connect inverters to rails """ """ Connect inverters to rails """
# connect nmos to gnd # connect nmos to gnd
gnd_posL = vector(self.i_nmosL.get_pin("S").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) gnd_posL = vector(self.inverter_nmos_left.get_pin("S").bc().x, self.gnd_position.y + drc["minwidth_metal1"])
self.add_path("metal1", [self.i_nmosL.get_pin("S").bc(), gnd_posL]) self.add_path("metal1", [self.inverter_nmos_left.get_pin("S").bc(), gnd_posL])
gnd_posR = vector(self.i_nmosR.get_pin("D").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) gnd_posR = vector(self.inverter_nmos_right.get_pin("D").bc().x, self.gnd_position.y + drc["minwidth_metal1"])
self.add_path("metal1", [self.i_nmosR.get_pin("D").bc(), gnd_posR]) self.add_path("metal1", [self.inverter_nmos_right.get_pin("D").bc(), gnd_posR])
# connect pmos to vdd # connect pmos to vdd
vdd_posL = vector(self.i_nmosL.get_pin("S").uc().x, self.vdd_position.y) vdd_posL = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y)
self.add_path("metal1", [self.i_pmosL.get_pin("S").uc(), vdd_posL]) self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_posL])
vdd_posR = vector(self.i_nmosR.get_pin("D").uc().x, self.vdd_position.y) vdd_posR = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y)
self.add_path("metal1", [self.i_pmosR.get_pin("D").uc(), vdd_posR]) self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_posR])
def add_write_transistors(self): 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 variables relevant to write transistors """
lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) # define offset correction due to rotation of the ptx cell
rit_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width write_rotation_correct = self.write_nmos.active_height
rot_correct = self.w_nmos.active_height
self.w_nmosL = [None] * self.num_write # define write transistor variables as empty arrays based on the number of write ports
self.w_nmosR = [None] * self.num_write self.write_nmos_left = [None] * self.num_write
self.write_nmos_right = [None] * self.num_write
self.wrow_positions = [None] * self.num_write self.wrow_positions = [None] * self.num_write
self.wbl_positions = [None] * self.num_write self.wbl_positions = [None] * self.num_write
self.wbl_bar_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): for k in range(0,self.num_write):
""" Add transistors and WROW lines """ """ Add transistors """
# calculate transistor offsets # calculate write transistor offsets
w_spacer = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] left_write_transistor_xpos = -self.inverter_tile_width \
wrow_ypos = self.gnd_position.y - (k+1)*2*drc["minwidth_metal2"] - (k+1)*self.write_tile_width + write_rotation_correct
lwt_xpos = lit_xpos - (k+1)*self.w_tile_width + rot_correct
rwt_xpos = rit_xpos + w_spacer + k*self.w_tile_width + rot_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 # add write transistors
self.w_nmosL[k] = self.add_inst(name="w_nmosL{}".format(k), self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
mod=self.w_nmos, mod=self.write_nmos,
offset=[lwt_xpos,0], offset=[left_write_transistor_xpos,0],
rotate=90) rotate=90)
self.connect_inst(["Q", "WROW{}".format(k), "WBL{}".format(k), "gnd"]) self.connect_inst(["Q", "wrow{}".format(k), "wbl{}".format(k), "gnd"])
self.w_nmosR[k] = self.add_inst(name="w_nmosR{}".format(k), self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
mod=self.w_nmos, mod=self.write_nmos,
offset=[rwt_xpos,0], offset=[right_write_transistor_xpos,0],
rotate=90) rotate=90)
self.connect_inst(["Q_bar", "WROW{}".format(k), "WBL_bar{}".format(k), "gnd"]) self.connect_inst(["Q_bar", "wrow{}".format(k), "wbl_bar{}".format(k), "gnd"])
# add WROW lines """ 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) self.wrow_positions[k] = vector(self.leftmost_xpos, wrow_ypos)
self.add_layout_pin(text="WROW{}".format(k),
# add pin for WROW
self.add_layout_pin(text="wrow{}".format(k),
layer="metal1", layer="metal1",
offset=self.wrow_positions[k], offset=self.wrow_positions[k],
width=self.cell_width, width=self.cell_width,
height=drc["minwidth_metal1"]) height=contact.m1m2.width)
""" Source/WBL/WBL_bar connections """ """ Source/WBL/WBL_bar connections """
# add contacts to connect source of wt to WBL or WBL_bar # add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar
offsetL = self.w_nmosL[k].get_pin("S").center() offsetL = self.write_nmos_left[k].get_pin("S").center()
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetL, offset=offsetL,
rotate=90) rotate=90)
offsetR = self.w_nmosR[k].get_pin("S").center() offsetR = self.write_nmos_right[k].get_pin("S").center()
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetR, offset=offsetR,
rotate=90) rotate=90)
# add WBL and WBL_bar (simultaneously connects to source of wt) # add pins for WBL and WBL_bar, overlaid on source contacts
self.wbl_positions[k] = vector(self.w_nmosL[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) 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), self.add_layout_pin(text="wbl{}".format(k),
layer="metal2", layer="metal2",
offset=self.wbl_positions[k], offset=self.wbl_positions[k],
width=drc["minwidth_metal2"], width=drc["minwidth_metal2"],
height=self.cell_height) height=self.cell_height)
self.wbl_bar_positions[k] = vector(self.w_nmosR[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) 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), self.add_layout_pin(text="wbl_bar{}".format(k),
layer="metal2", layer="metal2",
offset=self.wbl_bar_positions[k], offset=self.wbl_bar_positions[k],
width=drc["minwidth_metal2"], width=drc["minwidth_metal2"],
height=self.cell_height) height=self.cell_height)
""" Gate/WROW connections """ """ Gate/WROW connections """
# add contacts to connect gate of wt to WROW (poly to metal2) # add poly-to-meltal2 contacts to connect gate of write transistors to WROW (contact next to gate)
offsetL = vector(self.w_nmosL[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width, self.w_nmosL[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height) 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"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetL) offset=left_gate_contact)
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetL) offset=left_gate_contact)
self.add_rect_center(layer="poly", self.add_rect_center(layer="poly",
offset=offsetL, offset=left_gate_contact,
width=contact.poly.width, width=contact.poly.width,
height=contact.poly.height) height=contact.poly.height)
offsetR = vector(self.w_nmosR[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width, self.w_nmosR[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height)
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"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetR) offset=right_gate_contact)
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetR) offset=right_gate_contact)
self.add_rect_center(layer="poly", self.add_rect_center(layer="poly",
offset=offsetR, offset=right_gate_contact,
width=contact.poly.width, width=contact.poly.width,
height=contact.poly.height) height=contact.poly.height)
# connect gate of wt to contact # connect gate of write transistor to contact (poly path)
midL = vector(offsetL.x, self.w_nmosL[k].get_pin("G").lc().y) midL = vector(left_gate_contact.x, self.write_nmos_left[k].get_pin("G").lc().y)
self.add_path("poly", [self.w_nmosL[k].get_pin("G").lc(), midL, offsetL]) self.add_path("poly", [self.write_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact])
midR = vector(offsetR.x, self.w_nmosR[k].get_pin("G").rc().y) midR = vector(right_gate_contact.x, self.write_nmos_right[k].get_pin("G").rc().y)
self.add_path("poly", [self.w_nmosR[k].get_pin("G").rc(), midR, offsetR]) self.add_path("poly", [self.write_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact])
# add contacts to WROW lines # add metal1-to-metal2 contacts to WROW lines
offset = vector(offsetL.x, self.wrow_positions[k].y + 0.5*drc["minwidth_metal1"]) 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"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offset, offset=left_wrow_contact,
rotate=90) rotate=90)
offset = vector(offsetR.x, self.wrow_positions[k].y + 0.5*drc["minwidth_metal1"]) 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"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offset, offset=right_wrow_contact,
rotate=90) rotate=90)
# connect wt gate contacts to WROW contacts # connect write transistor gate contacts to WROW contacts (metal2 path)
wrow_offset = vector(offsetL.x, self.wrow_positions[k].y) self.add_path("metal2", [left_gate_contact, left_wrow_contact])
self.add_path("metal2", [offsetL, wrow_offset]) self.add_path("metal2", [right_gate_contact, right_wrow_contact])
wrow_offset = vector(offsetR.x, self.wrow_positions[k].y)
self.add_path("metal2", [offsetR, wrow_offset])
""" Drain/Storage connections """ """ Drain/Storage connections """
# this path only needs to be drawn once on the last iteration of the loop
if(k == self.num_write-1): if(k == self.num_write-1):
# add contacts to connect gate of inverters to drain of wt # add contacts to connect gate of inverters to drain of write transistors
offsetL = vector(self.i_nmosL.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_heightL) 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"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetL, offset=left_storage_contact,
rotate=90) rotate=90)
offsetR = vector(self.i_nmosR.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_heightL) 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"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetR, offset=right_storage_contact,
rotate=90) rotate=90)
# connect gate of inverters to contacts # connect gate of inverters to contacts (poly path)
gate_offsetL = vector(self.i_nmosL.get_pin("G").lc().x, offsetL.y) gate_offsetL = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos)
self.add_path("poly", [offsetL, gate_offsetL]) self.add_path("poly", [left_storage_contact, gate_offsetL])
gate_offsetR = vector(self.i_nmosR.get_pin("G").rc().x, offsetR.y) gate_offsetR = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos)
self.add_path("poly", [offsetR, gate_offsetR]) self.add_path("poly", [right_storage_contact, gate_offsetR])
# connect contacts to drains of wt # connect contacts to drains of write transistors (metal1 path)
midL0 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], offsetL.y) midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y)
midL1 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.w_nmosL[k].get_pin("D").lc().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", [offsetL, midL0, midL1, self.w_nmosL[k].get_pin("D").lc()]) self.add_path("metal1", [left_storage_contact, midL0, midL1, self.write_nmos_left[k].get_pin("D").lc()])
midR0 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], offsetR.y) midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y)
midR1 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.w_nmosR[k].get_pin("D").rc().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", [offsetR, midR0, midR1, self.w_nmosR[k].get_pin("D").rc()]) self.add_path("metal1", [right_storage_contact, midR0, midR1, self.write_nmos_right[k].get_pin("D").rc()])
def add_read_transistors(self): 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 also 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 variables relevant to read transistors """
lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) # define offset correction due to rotation of the ptx cell
rit_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width read_rotation_correct = self.read_nmos.active_height
lwt_xpos = lit_xpos - self.num_write*self.w_tile_width
rwt_xpos = rit_xpos + self.num_write*self.w_tile_width
wrow_ypos = self.gnd_position.y - self.num_write*2*drc["minwidth_metal2"]
rot_correct = self.r_nmos.active_height # calculate offset to overlap the drain of the read-access transistor with the source of the read transistor
r_spacer = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width overlap_offset = self.read_nmos.get_pin("D").ll() - self.read_nmos.get_pin("S").ll()
rw_spacer = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex
self.r_nmosL = [None] * self.num_read # define read transistor variables as empty arrays based on the number of read ports
self.r_nmosR = [None] * self.num_read 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.rrow_positions = [None] * self.num_read
self.rbl_positions = [None] * self.num_read self.rbl_positions = [None] * self.num_read
self.rbl_bar_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): for k in range(0,self.num_read):
""" Add transistors and RROW lines """ """ Add transistors """
# calculate transistor offsets # calculate transistor offsets
lrt_xpos = lwt_xpos - rw_spacer - self.r_nmos.active_height - k*self.r_tile_width + rot_correct left_read_transistor_xpos = -self.inverter_tile_width \
rrt_xpos = rwt_xpos + rw_spacer + k*self.r_tile_width + rot_correct - 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 # add read transistors
self.r_nmosL[k] = self.add_inst(name="r_nmosL", self.read_nmos_left[k] = self.add_inst(name="read_nmos_left",
mod=self.r_nmos, mod=self.read_nmos,
offset=[lrt_xpos,0], offset=[left_read_transistor_xpos,overlap_offset.x],
rotate=90) rotate=90)
self.connect_inst(["RROW{}".format(k), "Q", "RBL{}".format(k), "gnd"]) self.connect_inst(["rbl{}".format(k), "rrow{}".format(k), "RA_to_R_left{}".format(k), "gnd"])
self.r_nmosR[k] = self.add_inst(name="r_nmosR", self.read_nmos_right[k] = self.add_inst(name="read_nmos_right",
mod=self.r_nmos, mod=self.read_nmos,
offset=[rrt_xpos,0], offset=[right_read_transistor_xpos,overlap_offset.x],
rotate=90) rotate=90)
self.connect_inst(["RROW{}".format(k), "Q_bar", "RBL_bar{}".format(k), "gnd"]) self.connect_inst(["rbl_bar{}".format(k), "rrow{}".format(k), "RA_to_R_right{}".format(k), "gnd"])
# add RROW lines """ Add RROW lines """
rrow_ypos = wrow_ypos - (k+1)*2*drc["minwidth_metal2"] # 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) self.rrow_positions[k] = vector(self.leftmost_xpos, rrow_ypos)
self.add_layout_pin(text="RROW{}".format(k),
# add pin for RROW
self.add_layout_pin(text="rrow{}".format(k),
layer="metal1", layer="metal1",
offset=self.rrow_positions[k], offset=self.rrow_positions[k],
width=self.cell_width, width=self.cell_width,
height=drc["minwidth_metal1"]) height=contact.m1m2.width)
""" Source of read-access transistor / GND connection """
# connect source of read-access transistor to GND (metal1 path)
offset = 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(), offset])
""" Source of RA transistor / GND connection """ offset = vector(self.read_access_nmos_right[k].get_pin("S").bc().x, self.gnd_position.y)
offset = vector(self.r_nmosL[k].get_pins("S")[0].bc().x, self.gnd_position.y) self.add_path("metal1", [self.read_access_nmos_right[k].get_pin("S").bc(), offset])
self.add_path("metal1", [self.r_nmosL[k].get_pins("S")[0].bc(), offset])
offset = vector(self.r_nmosR[k].get_pins("S")[0].bc().x, self.gnd_position.y) """ Drain of read transistor / RBL & RBL_bar connection """
self.add_path("metal1", [self.r_nmosR[k].get_pins("S")[0].bc(), offset]) # add metal1-to-metal2 contacts on top of read transistor drain pins for connection to RBL and RBL_bar
offsetL = self.read_nmos_left[k].get_pin("D").center()
""" Source of R transistor / RBL & RBL_bar connection """
# add contacts to connect source of rt to RBL or RBL_bar
offsetL = self.r_nmosL[k].get_pins("S")[1].center()
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetL, offset=offsetL,
rotate=90) rotate=90)
offsetR = self.r_nmosR[k].get_pins("S")[1].center() offsetR = self.read_nmos_right[k].get_pin("D").center()
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetR, offset=offsetR,
rotate=90) rotate=90)
# add RBL and RBL_bar (simultaneously connects to source of rt) # add pins for RBL and RBL_bar, overlaid on drain contacts
self.rbl_positions[k] = vector(self.r_nmosL[k].get_pins("S")[1].center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) 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), self.add_layout_pin(text="rbl{}".format(k),
layer="metal2", layer="metal2",
offset=self.rbl_positions[k], offset=self.rbl_positions[k],
width=drc["minwidth_metal2"], width=drc["minwidth_metal2"],
height=self.cell_height) height=self.cell_height)
self.rbl_bar_positions[k] = vector(self.r_nmosR[k].get_pins("S")[1].center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) 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), self.add_layout_pin(text="rbl_bar{}".format(k),
layer="metal2", layer="metal2",
offset=self.rbl_bar_positions[k], offset=self.rbl_bar_positions[k],
width=drc["minwidth_metal2"], width=drc["minwidth_metal2"],
height=self.cell_height) height=self.cell_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 - read_rotation_correct - 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)
""" Gate of R transistor / RROW connection """
# add contact to connect gate of rt to RROW (poly to metal2)
offsetL = vector(lrt_xpos - rot_correct - drc["minwidth_poly"] - 0.5*contact.poly.width, self.r_nmosL[k].get_pins("G")[1].lc().y)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetL) offset=left_gate_contact)
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetL) 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)
offsetR = vector(rrt_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width, self.r_nmosR[k].get_pins("G")[1].rc().y)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetR) offset=right_gate_contact)
self.add_contact_center(layers=("metal1", "via1", "metal2"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=offsetR) offset=right_gate_contact)
# connect gate of rt to contact # connect gate of read transistor to contact (poly path)
self.add_path("poly", [self.r_nmosL[k].get_pins("G")[1].lc(), offsetL]) self.add_path("poly", [self.read_nmos_left[k].get_pin("G").lc(), left_gate_contact])
self.add_path("poly", [self.r_nmosR[k].get_pins("G")[1].rc(), offsetR]) self.add_path("poly", [self.read_nmos_right[k].get_pin("G").rc(), right_gate_contact])
# add contacts to RROW lines # add metal1-to-metal2 contacts to RROW lines
row_offsetL = vector(self.r_nmosL[k].get_pins("S")[1].lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width, self.rrow_positions[k].y + 0.5*drc["minwidth_metal1"]) 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"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=row_offsetL, offset=left_rrow_contact,
rotate=90) rotate=90)
row_offsetR = vector(self.r_nmosR[k].get_pins("S")[1].rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width, self.rrow_positions[k].y + 0.5*drc["minwidth_metal1"]) 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"), self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=row_offsetR, offset=right_rrow_contact,
rotate=90) rotate=90)
# connect rt gate contacts to RROW contacts # connect read transistor gate contacts to RROW contacts (metal2 path)
self.add_path("metal2", [offsetL, row_offsetL]) self.add_path("metal2", [left_gate_contact, left_rrow_contact])
self.add_path("metal2", [offsetR, row_offsetR]) self.add_path("metal2", [right_gate_contact, right_rrow_contact])
""" 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)
""" Gate of RA transistor / storage connection """
# add contact to connect gate of rat to output of inverters
offsetL = vector(lrt_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width, self.r_nmosL[k].get_pins("G")[0].rc().y)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetL) offset=left_gate_contact)
contact_xpos = right_read_transistor_xpos - read_rotation_correct - 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)
offsetR = vector(rrt_xpos - rot_correct - drc["minwidth_poly"] - 0.5*contact.poly.width, self.r_nmosR[k].get_pins("G")[0].lc().y)
self.add_contact_center(layers=("poly", "contact", "metal1"), self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=offsetR) offset=right_gate_contact)
# connect gate of rat to contact # connect gate of read-access transistor to contact (poly path)
self.add_path("poly", [self.r_nmosL[k].get_pins("G")[0].rc(), offsetL]) self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").rc(), left_gate_contact])
self.add_path("poly", [self.r_nmosR[k].get_pins("G")[0].lc(), offsetR]) self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").lc(), right_gate_contact])
# connect contact to output of inverters # connect contact to output of inverters (metal1 path)
midL0 = vector(offsetL.x, self.r_nmosL[k].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) # metal1 path must route over the read transistors (above drain of read transistor)
midL1 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.r_nmosL[0].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) midL0 = vector(left_gate_contact.x, self.read_nmos_left[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
midL2 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.cross_heightU) ## continue metal1 path until inverter
gate_offsetL = vector(self.i_nmosL.get_pin("D").center().x, self.cross_heightU) 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"])
self.add_path("metal1", [offsetL, midL0, midL1, midL2, gate_offsetL]) ## route down to be level with inverter output
midL2 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos)
## endpoint at drain of inverter
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(offsetR.x, self.r_nmosR[k].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) midR0 = vector(right_gate_contact.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
midR1 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.r_nmosR[k].get_pins("S")[1].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.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.cross_heightU) midR2 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos)
gate_offsetR = vector(self.i_nmosR.get_pin("S").center().x, self.cross_heightU) right_inverter_offset = vector(self.inverter_nmos_right.get_pin("S").center().x, self.cross_couple_upper_ypos)
self.add_path("metal1", [offsetR, midR0, midR1, midR2, gate_offsetR]) self.add_path("metal1", [right_gate_contact, midR0, midR1, midR2, right_inverter_offset])
def extend_well(self): def extend_well(self):
""" extend nwell and pwell """ """ extend pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well """
# extend pwell to encompass i_nmos
offset = vector(self.leftmost_xpos, self.botmost_ypos) offset = vector(self.leftmost_xpos, self.botmost_ypos)
well_height = -self.botmost_ypos + self.i_nmos.well_height - drc["well_enclosure_active"] well_height = -self.botmost_ypos + self.inverter_nmos.cell_well_height - drc["well_enclosure_active"]
self.add_rect(layer="pwell", self.add_rect(layer="pwell",
offset=offset, offset=offset,
width=self.cell_width, width=self.cell_width,
height=well_height) height=well_height)
# extend pwell to encompass r_nmos """ extend pwell over write transistors to the height of the write transistor well """
r_well_width = self.num_read*self.r_tile_width left_write_well_xpos = -(self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"])
r_well_height = self.r_nmos.well_width - drc["well_enclosure_active"] right_write_well_xpos = self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"]
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 read transistor well """
if(self.num_read > 0):
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"]
read_well_width = -(self.leftmost_xpos - left_read_well_xpos)
read_well_height = self.topmost_ypos
offset = vector(self.leftmost_xpos, 0) offset = vector(self.leftmost_xpos, 0)
self.add_rect(layer="pwell", self.add_rect(layer="pwell",
offset=offset, offset=offset,
width=r_well_width, width=read_well_width,
height=r_well_height) height=read_well_height)
offset = vector(-self.leftmost_xpos - r_well_width, 0) offset = vector(right_read_well_xpos, 0)
self.add_rect(layer="pwell", self.add_rect(layer="pwell",
offset=offset, offset=offset,
width=r_well_width, width=read_well_width,
height=r_well_height) height=read_well_height)
# extend pwell to encompass w_nmos """ extend nwell to encompass inverter_pmos """
lwt_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"] + self.w_tile_width - self.w_nmos.active_height - drc["well_enclosure_active"]) inverter_well_xpos = -self.inverter_tile_width - drc["well_enclosure_active"]
rwt_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width + self.w_tile_width - self.w_nmos.active_height - drc["well_enclosure_active"] inverter_well_ypos = self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"]
w_well_width = -(self.leftmost_xpos - lwt_xpos) well_width = 2*self.inverter_pmos.active_width + 3*parameter["min_tx_size"] + 2*drc["well_enclosure_active"]
w_well_height = self.w_nmos.well_width - drc["well_enclosure_active"] well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
offset = vector(lwt_xpos - w_well_width, 0)
self.add_rect(layer="pwell",
offset=offset,
width=w_well_width,
height=w_well_height)
offset = vector(rwt_xpos, 0) offset = [inverter_well_xpos,inverter_well_ypos]
self.add_rect(layer="pwell",
offset=offset,
width=w_well_width,
height=w_well_height)
# extend nwell to encompass i_pmos
lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"] + drc["well_enclosure_active"])
it_ypos = self.inv_gap + self.i_nmos.active_height - drc["well_enclosure_active"]
offset = [lit_xpos,it_ypos]
well_width = 2*self.i_pmos.active_width + 3*parameter["min_tx_size"] + 2*drc["well_enclosure_active"]
well_height = self.vdd_position.y - it_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"]
self.add_rect(layer="nwell", self.add_rect(layer="nwell",
offset=offset, offset=offset,
width=well_width, width=well_width,
@ -547,7 +646,7 @@ class pbitcell(design.design):
""" add well contacts """ """ add well contacts """
# connect pimplants to gnd # connect pimplants to gnd
offset = vector(0, self.gnd_position.y + 0.5*drc["minwidth_metal1"]) offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width)
self.add_contact_center(layers=("active", "contact", "metal1"), self.add_contact_center(layers=("active", "contact", "metal1"),
offset=offset, offset=offset,
rotate=90) rotate=90)
@ -570,7 +669,7 @@ class pbitcell(design.design):
def add_fail(self): def add_fail(self):
# for failing drc # for failing drc when I want to observe the gds layout
frail_width = self.well_width = 2*drc["minwidth_metal1"] frail_width = self.well_width = 2*drc["minwidth_metal1"]
frail_height = self.rail_height = drc["minwidth_metal1"] frail_height = self.rail_height = drc["minwidth_metal1"]

View File

@ -0,0 +1,43 @@
#!/usr/bin/env python2.7
"""
Run regresion tests on a parameterized bitcell
"""
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
import debug
OPTS = globals.OPTS
#@unittest.skip("SKIPPING 04_pbitcell_test")
class pbitcell_test(openram_test):
def runTest(self):
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
global verify
import verify
OPTS.check_lvsdrc = False
import pbitcell
import tech
debug.info(2, "Bitcell with 2 write ports and 0 read ports")
tx = pbitcell.pbitcell(num_write=2,num_read=0)
self.local_check(tx)
OPTS.check_lvsdrc = True
globals.end_openram()
# 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()

View File

@ -1,60 +0,0 @@
#!/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_pbitcell_test")
class pbitcell_test(unittest.TestCase):
def runTest(self):
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
OPTS.check_lvsdrc = False
import pbitcell
import tech
debug.info(2, "Test for pbitcell with 0 write ports and 2 read ports")
tx = pbitcell.pbitcell(num_write=0,num_read=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()

View File

@ -1,56 +1,39 @@
#!/usr/bin/env python2.7 #!/usr/bin/env python2.7
""" """
Run regresion tests on a parameterized inverter Run regresion tests on a parameterized bitcell
""" """
import unittest import unittest
from testutils import header from testutils import header,openram_test
import sys,os import sys,os
sys.path.append(os.path.join(sys.path[0],"..")) sys.path.append(os.path.join(sys.path[0],".."))
import globals import globals
from globals import OPTS
import debug import debug
import verify
OPTS = globals.OPTS OPTS = globals.OPTS
#@unittest.skip("SKIPPING 04_pbitcell_test") #@unittest.skip("SKIPPING 04_pbitcell_test")
class pbitcell_test(unittest.TestCase): class pbitcell_test(openram_test):
def runTest(self): def runTest(self):
globals.init_openram("config_20_{0}".format(OPTS.tech_name)) globals.init_openram("config_20_{0}".format(OPTS.tech_name))
global verify
import verify
OPTS.check_lvsdrc = False OPTS.check_lvsdrc = False
import pbitcell import pbitcell
import tech import tech
debug.info(2, "Test for pbitcell with 2 write ports and 2 read ports") debug.info(2, "Bitcell with 2 write ports and 2 read ports")
tx = pbitcell.pbitcell(num_write=2,num_read=2) tx = pbitcell.pbitcell(num_write=2,num_read=2)
self.local_check(tx) self.local_check(tx)
OPTS.check_lvsdrc = True OPTS.check_lvsdrc = True
globals.end_openram() 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 # instantiate a copy of the class to actually run the test
if __name__ == "__main__": if __name__ == "__main__":