OpenRAM/compiler/bitcells/pbitcell.py

902 lines
47 KiB
Python
Raw Normal View History

import contact
import design
import debug
from tech import drc, parameter, spice
from vector import vector
from ptx import ptx
from globals import OPTS
class pbitcell(design.design):
"""
This module implements a parametrically sized multi-port bitcell,
with a variable number of read/write, write, and read ports
"""
def __init__(self, replica_bitcell=False):
self.num_rw_ports = OPTS.num_rw_ports
self.num_w_ports = OPTS.num_w_ports
self.num_r_ports = OPTS.num_r_ports
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
self.replica_bitcell = replica_bitcell
if self.replica_bitcell:
name = "replica_pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
else:
name = "pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports)
design.design.__init__(self, name)
debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports,
self.num_w_ports,
self.num_r_ports))
self.create_netlist()
# We must always create the bitcell layout because some transistor sizes in the other netlists depend on it
self.create_layout()
def create_netlist(self):
self.add_pins()
self.add_modules()
self.create_storage()
if(self.num_rw_ports > 0):
self.create_readwrite_ports()
if(self.num_w_ports > 0):
self.create_write_ports()
if(self.num_r_ports > 0):
self.create_read_ports()
def create_layout(self):
self.calculate_spacing()
self.calculate_postions()
self.place_storage()
self.route_storage()
self.route_rails()
if(self.num_rw_ports > 0):
self.place_readwrite_ports()
self.route_readwrite_access()
if(self.num_w_ports > 0):
self.place_write_ports()
self.route_write_access()
if(self.num_r_ports > 0):
self.place_read_ports()
self.route_read_access()
self.extend_well()
self.route_wordlines()
self.route_bitlines()
self.route_supply()
if self.replica_bitcell:
self.route_rbc_short()
# in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible
# this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though
if not OPTS.netlist_only:
self.offset_all_coordinates()
gnd_overlap = vector(0, 0.5*contact.well.width)
self.translate_all(gnd_overlap)
self.DRC_LVS()
def add_pins(self):
""" add pins and set names for bitlines and wordlines """
self.rw_bl_names = []
self.rw_br_names = []
self.w_bl_names = []
self.w_br_names = []
self.r_bl_names = []
self.r_br_names = []
self.rw_wl_names = []
self.w_wl_names = []
self.r_wl_names = []
port = 0
for k in range(self.num_rw_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.rw_bl_names.append("bl{}".format(port))
self.rw_br_names.append("br{}".format(port))
port += 1
for k in range(self.num_w_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.w_bl_names.append("bl{}".format(port))
self.w_br_names.append("br{}".format(port))
port += 1
for k in range(self.num_r_ports):
self.add_pin("bl{}".format(port))
self.add_pin("br{}".format(port))
self.r_bl_names.append("bl{}".format(port))
self.r_br_names.append("br{}".format(port))
port += 1
port = 0
for k in range(self.num_rw_ports):
self.add_pin("wl{}".format(port))
self.rw_wl_names.append("wl{}".format(port))
port += 1
for k in range(self.num_w_ports):
self.add_pin("wl{}".format(port))
self.w_wl_names.append("wl{}".format(port))
port += 1
for k in range(self.num_r_ports):
self.add_pin("wl{}".format(port))
self.r_wl_names.append("wl{}".format(port))
port += 1
self.add_pin("vdd")
self.add_pin("gnd")
# if this is a replica bitcell, replace the instances of Q_bar with vdd
if self.replica_bitcell:
self.Q_bar = "vdd"
else:
self.Q_bar = "Q_bar"
def add_modules(self):
""" Determine size of transistors and add ptx modules """
# if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports
if(self.num_rw_ports > 0):
inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"]
inverter_pmos_width = parameter["6T_inv_pmos_size"]
readwrite_nmos_width = parameter["6T_access_size"]
write_nmos_width = parameter["6T_access_size"]
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
# if there are no read/write ports, then the inverter nmos is statically sized for the dual port case
else:
inverter_nmos_width = 2*parameter["6T_inv_pmos_size"]
inverter_pmos_width = parameter["6T_inv_pmos_size"]
readwrite_nmos_width = parameter["6T_access_size"]
write_nmos_width = parameter["6T_access_size"]
read_nmos_width = 2*parameter["6T_inv_pmos_size"]
# 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 readwrite transitors
self.readwrite_nmos = ptx(width=readwrite_nmos_width,
tx_type="nmos")
self.add_mod(self.readwrite_nmos)
# 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 calculate_spacing(self):
""" Calculate transistor spacings """
# calculate metal contact extensions over transistor active
readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height)
max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension)
# y-offset for the access transistor's gate contact
self.gate_contact_yoffset = max_contact_extension + self.m2_space + 0.5*max(contact.poly.height, contact.m1m2.height)
# y-position of access transistors
self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset
# y-position of inverter nmos
self.inverter_nmos_ypos = self.port_ypos
# spacing between ports
self.bitline_offset = -self.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width
self.port_spacing = self.bitline_offset + self.m2_space
# spacing between cross coupled inverters
self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space
# calculations related to inverter connections
inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
inverter_nmos_contact_extension = 0.5*(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height)
self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
+ self.poly_to_polycontact + 2*contact.poly.width \
+ self.m1_space + inverter_pmos_contact_extension
self.cross_couple_lower_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
+ 0.5*contact.poly.width
self.cross_couple_upper_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \
+ max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \
+ self.poly_to_polycontact \
+ 1.5*contact.poly.width
# spacing between wordlines (and gnd)
self.rowline_spacing = self.m1_space + contact.m1m2.width
self.rowline_offset = -0.5*self.m1_width
# spacing for vdd
implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - self.m1_width)
metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space
self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width
# read port dimensions
width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx()
self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction
def calculate_postions(self):
""" Calculate positions that describe the edges and dimensions of the cell """
self.botmost_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing
self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \
- self.num_rw_ports*(self.readwrite_nmos.active_width + self.port_spacing) \
- self.num_w_ports*(self.write_nmos.active_width + self.port_spacing) \
- self.num_r_ports*(self.read_port_width + self.port_spacing) \
- self.bitline_offset - 0.5*self.m2_width
self.width = -2*self.leftmost_xpos
self.height = self.topmost_ypos - self.botmost_ypos
self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos)
def create_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".
"""
# create active for nmos
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
mod=self.inverter_nmos)
self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"])
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
mod=self.inverter_nmos)
self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"])
# create active for pmos
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
mod=self.inverter_pmos)
self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"])
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
mod=self.inverter_pmos)
self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"])
def place_storage(self):
""" Places the transistors for the crossed coupled inverters in the bitcell """
# 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_ypos + self.inverter_nmos.active_height + self.inverter_gap
# create active for nmos
self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos])
self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos])
# create active for pmos
self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos])
self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos])
# update furthest left and right transistor edges (this will propagate to further transistor offset calculations)
self.left_building_edge = left_inverter_xpos
self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width
def route_storage(self):
""" Routes inputs and outputs of inverters to cross couple them """
# 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 connections)
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 route_rails(self):
""" Adds gnd and vdd rails and connects them to the inverters """
# Add rails for vdd and gnd
gnd_ypos = self.rowline_offset - self.total_ports*self.rowline_spacing
self.gnd_position = vector(0, gnd_ypos)
self.gnd = self.add_layout_pin_rect_center(text="gnd",
layer="metal1",
offset=self.gnd_position,
width=self.width,
height=self.m1_width)
vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset
self.vdd_position = vector(0, vdd_ypos)
self.vdd = self.add_layout_pin_rect_center(text="vdd",
layer="metal1",
offset=self.vdd_position,
width=self.width,
height=self.m1_width)
def create_readwrite_ports(self):
"""
Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell.
A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor.
The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q).
In a write operation, driving RWBL high or low sets the value of the cell.
In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell.
This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar.
"""
# define read/write transistor variables as empty arrays based on the number of read/write ports
self.readwrite_nmos_left = [None] * self.num_rw_ports
self.readwrite_nmos_right = [None] * self.num_rw_ports
# iterate over the number of read/write ports
for k in range(0,self.num_rw_ports):
# add read/write transistors
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
mod=self.readwrite_nmos)
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"])
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
mod=self.readwrite_nmos)
self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"])
def place_readwrite_ports(self):
""" Places read/write ports in the bit cell """
# define read/write transistor variables as empty arrays based on the number of read/write ports
self.rwwl_positions = [None] * self.num_rw_ports
self.rwbl_positions = [None] * self.num_rw_ports
self.rwbr_positions = [None] * self.num_rw_ports
# iterate over the number of read/write ports
for k in range(0,self.num_rw_ports):
# calculate read/write transistor offsets
left_readwrite_transistor_xpos = self.left_building_edge \
- (k+1)*self.port_spacing \
- (k+1)*self.readwrite_nmos.active_width
right_readwrite_transistor_xpos = self.right_building_edge \
+ (k+1)*self.port_spacing \
+ k*self.readwrite_nmos.active_width
# place read/write transistors
self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos])
self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos])
# add pin for RWWL
rwwl_ypos = self.rowline_offset - k*self.rowline_spacing
self.rwwl_positions[k] = vector(0, rwwl_ypos)
self.add_layout_pin_rect_center(text=self.rw_wl_names[k],
layer="metal1",
offset=self.rwwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for RWBL and RWBR
rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + self.m2_width
self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.rw_bl_names[k],
layer="metal2",
offset=self.rwbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.bitline_offset - self.m2_width
self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.rw_br_names[k],
layer="metal2",
offset=self.rwbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
# update furthest left and right transistor edges
self.left_building_edge = left_readwrite_transistor_xpos
self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width
def create_write_ports(self):
"""
Creates write ports in the bit cell. A differential pair of transistors can write only.
A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor.
The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q).
In a write operation, 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 WBR to Q_bar.
"""
# define write transistor variables as empty arrays based on the number of write ports
self.write_nmos_left = [None] * self.num_w_ports
self.write_nmos_right = [None] * self.num_w_ports
# iterate over the number of write ports
for k in range(0,self.num_w_ports):
# add write transistors
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
mod=self.write_nmos)
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"])
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
mod=self.write_nmos)
self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"])
def place_write_ports(self):
""" Places write ports in the bit cell """
# define write transistor variables as empty arrays based on the number of write ports
self.wwl_positions = [None] * self.num_w_ports
self.wbl_positions = [None] * self.num_w_ports
self.wbr_positions = [None] * self.num_w_ports
# iterate over the number of write ports
for k in range(0,self.num_w_ports):
# Add transistors
# calculate write transistor offsets
left_write_transistor_xpos = self.left_building_edge \
- (k+1)*self.port_spacing \
- (k+1)*self.write_nmos.active_width
right_write_transistor_xpos = self.right_building_edge \
+ (k+1)*self.port_spacing \
+ k*self.write_nmos.active_width
# add write transistors
self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos])
self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos])
# add pin for WWL
wwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing
self.wwl_positions[k] = vector(0, wwl_ypos)
self.add_layout_pin_rect_center(text=self.w_wl_names[k],
layer="metal1",
offset=self.wwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for WBL and WBR
wbl_xpos = left_write_transistor_xpos - self.bitline_offset + self.m2_width
self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.w_bl_names[k],
layer="metal2",
offset=self.wbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset - self.m2_width
self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.w_br_names[k],
layer="metal2",
offset=self.wbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
# update furthest left and right transistor edges
self.left_building_edge = left_write_transistor_xpos
self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width
def create_read_ports(self):
"""
Creates read ports in the bit cell. A differential pair of ports can read only.
Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor".
The read transistor is connected to RWL (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 (RWL) 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 read transistor variables as empty arrays based on the number of read ports
self.read_nmos_left = [None] * self.num_r_ports
self.read_nmos_right = [None] * self.num_r_ports
self.read_access_nmos_left = [None] * self.num_r_ports
self.read_access_nmos_right = [None] * self.num_r_ports
# iterate over the number of read ports
for k in range(0,self.num_r_ports):
# add read-access transistors
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
mod=self.read_nmos)
self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"])
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
mod=self.read_nmos)
self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"])
# add read transistors
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k),
mod=self.read_nmos)
self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"])
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k),
mod=self.read_nmos)
self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"])
def place_read_ports(self):
""" Places the read ports in the bit cell """
# define read transistor variables as empty arrays based on the number of read ports
self.rwl_positions = [None] * self.num_r_ports
self.rbl_positions = [None] * self.num_r_ports
self.rbr_positions = [None] * self.num_r_ports
# 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").cx() - self.read_nmos.get_pin("S").cx()
# iterate over the number of read ports
for k in range(0,self.num_r_ports):
# calculate transistor offsets
left_read_transistor_xpos = self.left_building_edge \
- (k+1)*self.port_spacing \
- (k+1)*self.read_port_width
right_read_transistor_xpos = self.right_building_edge \
+ (k+1)*self.port_spacing \
+ k*self.read_port_width
# add read-access transistors
self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos])
self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos])
# add read transistors
self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos])
self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos])
# add pin for RWL
rwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing
self.rwl_positions[k] = vector(0, rwl_ypos)
self.add_layout_pin_rect_center(text=self.r_wl_names[k],
layer="metal1",
offset=self.rwl_positions[k],
width=self.width,
height=self.m1_width)
# add pins for RBL and RBR
rbl_xpos = left_read_transistor_xpos - self.bitline_offset + self.m2_width
self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.r_bl_names[k],
layer="metal2",
offset=self.rbl_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset - self.m2_width
self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos)
self.add_layout_pin_rect_center(text=self.r_br_names[k],
layer="metal2",
offset=self.rbr_positions[k],
width=drc["minwidth_metal2"],
height=self.height)
def route_wordlines(self):
""" Routes gate of transistors to their respective wordlines """
port_transistors = []
for k in range(self.num_rw_ports):
port_transistors.append(self.readwrite_nmos_left[k])
port_transistors.append(self.readwrite_nmos_right[k])
for k in range(self.num_w_ports):
port_transistors.append(self.write_nmos_left[k])
port_transistors.append(self.write_nmos_right[k])
for k in range(self.num_r_ports):
port_transistors.append(self.read_nmos_left[k])
port_transistors.append(self.read_nmos_right[k])
wl_positions = []
for k in range(self.num_rw_ports):
wl_positions.append(self.rwwl_positions[k])
wl_positions.append(self.rwwl_positions[k])
for k in range(self.num_w_ports):
wl_positions.append(self.wwl_positions[k])
wl_positions.append(self.wwl_positions[k])
for k in range(self.num_r_ports):
wl_positions.append(self.rwl_positions[k])
wl_positions.append(self.rwl_positions[k])
for k in range(2*self.total_ports):
gate_offset = port_transistors[k].get_pin("G").bc()
port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active)
wl_contact_offset = vector(gate_offset.x, wl_positions[k].y)
# first transistor on either side of the cross coupled inverters does not need to route to wordline on metal2
if (k == 0) or (k == 1):
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [gate_offset, port_contact_offset])
self.add_path("metal1", [port_contact_offset, wl_contact_offset])
else:
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offset)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=wl_contact_offset,
rotate=90)
self.add_path("poly", [gate_offset, port_contact_offset])
self.add_path("metal2", [port_contact_offset, wl_contact_offset])
def route_bitlines(self):
""" Routes read/write transistors to their respective bitlines """
left_port_transistors = []
right_port_transistors = []
for k in range(self.num_rw_ports):
left_port_transistors.append(self.readwrite_nmos_left[k])
right_port_transistors.append(self.readwrite_nmos_right[k])
for k in range(self.num_w_ports):
left_port_transistors.append(self.write_nmos_left[k])
right_port_transistors.append(self.write_nmos_right[k])
for k in range(self.num_r_ports):
left_port_transistors.append(self.read_nmos_left[k])
right_port_transistors.append(self.read_nmos_right[k])
bl_positions = []
br_positions = []
for k in range(self.num_rw_ports):
bl_positions.append(self.rwbl_positions[k])
br_positions.append(self.rwbr_positions[k])
for k in range(self.num_w_ports):
bl_positions.append(self.wbl_positions[k])
br_positions.append(self.wbr_positions[k])
for k in range(self.num_r_ports):
bl_positions.append(self.rbl_positions[k])
br_positions.append(self.rbr_positions[k])
for k in range(self.total_ports):
port_contact_offest = left_port_transistors[k].get_pin("S").center()
bl_offset = vector(bl_positions[k].x, port_contact_offest.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offest)
self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height)
for k in range(self.total_ports):
port_contact_offest = right_port_transistors[k].get_pin("D").center()
br_offset = vector(br_positions[k].x, port_contact_offest.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=port_contact_offest)
self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height)
def route_supply(self):
""" Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """
# route inverter nmos and read-access nmos to gnd
nmos_contact_positions = []
nmos_contact_positions.append(self.inverter_nmos_left.get_pin("S").center())
nmos_contact_positions.append(self.inverter_nmos_right.get_pin("D").center())
for k in range(self.num_r_ports):
nmos_contact_positions.append(self.read_access_nmos_left[k].get_pin("D").center())
nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center())
for position in nmos_contact_positions:
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=position)
supply_offset = vector(position.x, self.gnd_position.y)
self.add_contact_center(layers=("metal1", "via1", "metal2"),
offset=supply_offset,
rotate=90)
self.add_path("metal2", [position, supply_offset])
# route 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 route_readwrite_access(self):
""" Routes read/write transistors to the storage component of the bitcell """
for k in range(self.num_rw_ports):
mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
self.add_path("metal1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_pos])
mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
self.add_path("metal1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_bar_pos])
def route_write_access(self):
""" Routes read/write transistors to the storage component of the bitcell """
for k in range(self.num_w_ports):
mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos)
Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos)
self.add_path("metal1", [self.write_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_pos])
mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos)
Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos)
self.add_path("metal1", [self.write_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, Q_bar_pos])
def route_read_access(self):
""" Routes read access transistors to the storage component of the bitcell """
# add poly to metal1 contacts for gates of the inverters
left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_upper_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_polycontact"] + 0.5*contact.poly.width, self.cross_couple_upper_ypos)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=right_storage_contact,
rotate=90)
inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_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_upper_ypos)
self.add_path("poly", [right_storage_contact, inverter_gate_offset_right])
# add poly to metal1 contacts for gates of read-access transistors
# route from read-access contacts to inverter contacts on metal1
for k in range(self.num_r_ports):
port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset])
mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, left_storage_contact])
port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active)
self.add_contact_center(layers=("poly", "contact", "metal1"),
offset=port_contact_offset)
self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset])
mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos)
self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width)
self.add_path("metal1", [mid, right_storage_contact])
def extend_well(self):
"""
Connects wells between ptx modules and places well contacts"""
# extend pwell to encompass entire nmos region of the cell up to the height of the tallest nmos transistor
max_nmos_well_height = max(self.inverter_nmos.cell_well_height,
self.readwrite_nmos.cell_well_height,
self.write_nmos.cell_well_height,
self.read_nmos.cell_well_height)
well_height = max_nmos_well_height + self.port_ypos - self.well_enclose_active - self.gnd_position.y
offset = vector(self.leftmost_xpos, self.botmost_ypos)
self.add_rect(layer="pwell",
offset=offset,
width=self.width,
height=well_height)
# extend nwell to encompass inverter_pmos
# calculate offset of the left pmos well
inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) - drc["well_enclosure_active"]
inverter_well_ypos = self.inverter_nmos_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_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) + 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)
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=offset,
rotate=90,
implant_type="p",
well_type="p")
# connect nimplants to vdd
offset = vector(0, self.vdd_position.y)
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=offset,
rotate=90,
implant_type="n",
well_type="n")
def list_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
bitcell_pins = []
for port in range(self.total_ports):
bitcell_pins.append("bl{0}_{1}".format(port,col))
bitcell_pins.append("br{0}_{1}".format(port,col))
for port in range(self.total_ports):
bitcell_pins.append("wl{0}_{1}".format(port,row))
bitcell_pins.append("vdd")
bitcell_pins.append("gnd")
return bitcell_pins
def list_all_wl_names(self):
""" Creates a list of all wordline pin names """
wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names
return wordline_names
def list_all_bitline_names(self):
""" Creates a list of all bitline pin names (both bl and br) """
bitline_pins = []
for port in range(self.total_ports):
bitline_pins.append("bl{0}".format(port))
bitline_pins.append("br{0}".format(port))
return bitline_pins
def list_all_bl_names(self):
""" Creates a list of all bl pins names """
bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names
return bl_pins
def list_all_br_names(self):
""" Creates a list of all br pins names """
br_pins = self.rw_br_names + self.w_br_names + self.r_br_names
return br_pins
def list_read_bl_names(self):
""" Creates a list of bl pin names associated with read ports """
bl_pins = self.rw_bl_names + self.r_bl_names
return bl_pins
def list_read_br_names(self):
""" Creates a list of br pin names associated with read ports """
br_pins = self.rw_br_names + self.r_br_names
return br_pins
def list_write_bl_names(self):
""" Creates a list of bl pin names associated with write ports """
bl_pins = self.rw_bl_names + self.w_bl_names
return bl_pins
def list_write_br_names(self):
""" Creates a list of br pin names asscociated with write ports"""
br_pins = self.rw_br_names + self.w_br_names
return br_pins
def route_rbc_short(self):
""" route the short from Q_bar to gnd necessary for the replica bitcell """
Q_bar_pos = self.inverter_pmos_right.get_pin("S").center()
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
self.add_path("metal1", [Q_bar_pos, vdd_pos])
def analytical_delay(self, slew, load=0, swing = 0.5):
#FIXME: Delay copied exactly over from bitcell
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
return result
def analytical_power(self, proc, vdd, temp, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 #temporary
total_power = self.return_power(dynamic, leakage)
return total_power