2018-08-07 18:44:01 +02:00
|
|
|
import contact
|
|
|
|
|
import design
|
|
|
|
|
import debug
|
|
|
|
|
from tech import drc, parameter, spice
|
|
|
|
|
from vector import vector
|
|
|
|
|
from ptx import ptx
|
|
|
|
|
from globals import OPTS
|
|
|
|
|
|
2018-09-04 20:55:22 +02:00
|
|
|
class pbitcell(design.design):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
|
|
|
|
This module implements a parametrically sized multi-port bitcell,
|
|
|
|
|
with a variable number of read/write, write, and read ports
|
|
|
|
|
"""
|
2018-09-07 02:59:21 +02:00
|
|
|
|
2018-09-14 01:53:24 +02:00
|
|
|
def __init__(self, replica_bitcell=False):
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-09-04 19:47:24 +02:00
|
|
|
self.num_rw_ports = OPTS.num_rw_ports
|
|
|
|
|
self.num_w_ports = OPTS.num_w_ports
|
|
|
|
|
self.num_r_ports = OPTS.num_r_ports
|
2018-09-07 04:36:50 +02:00
|
|
|
self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports
|
2018-09-14 01:53:24 +02:00
|
|
|
|
|
|
|
|
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)
|
2018-09-04 20:55:22 +02:00
|
|
|
# This is not a pgate because pgates depend on the bitcell height!
|
|
|
|
|
design.design.__init__(self, name)
|
2018-09-04 19:47:24 +02:00
|
|
|
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))
|
2018-09-07 02:59:21 +02:00
|
|
|
|
2018-08-27 23:18:32 +02:00
|
|
|
self.create_netlist()
|
2018-09-04 20:55:22 +02:00
|
|
|
# We must always create the bitcell layout because
|
|
|
|
|
# some transistor sizes in the other netlists depend on it
|
|
|
|
|
self.create_layout()
|
2018-09-13 10:42:06 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
def create_netlist(self):
|
|
|
|
|
self.add_pins()
|
|
|
|
|
self.add_modules()
|
|
|
|
|
self.create_storage()
|
|
|
|
|
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_rw_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.create_readwrite_ports()
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_w_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.create_write_ports()
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_r_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.create_read_ports()
|
|
|
|
|
|
|
|
|
|
def create_layout(self):
|
|
|
|
|
self.calculate_spacing()
|
|
|
|
|
self.calculate_postions()
|
|
|
|
|
|
|
|
|
|
self.place_storage()
|
|
|
|
|
self.route_storage()
|
|
|
|
|
self.route_rails()
|
|
|
|
|
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_rw_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.place_readwrite_ports()
|
2018-09-07 02:59:21 +02:00
|
|
|
self.route_readwrite_wordlines()
|
|
|
|
|
self.route_readwrite_bitlines()
|
2018-09-07 04:36:50 +02:00
|
|
|
if(self.num_w_ports == 0): # routing for write to storage is the same as read/write to storage
|
2018-09-07 02:59:21 +02:00
|
|
|
self.route_readwrite_access()
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_w_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.place_write_ports()
|
2018-09-07 02:59:21 +02:00
|
|
|
self.route_write_wordlines()
|
|
|
|
|
self.route_write_bitlines()
|
|
|
|
|
self.route_write_access()
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_r_ports > 0):
|
2018-08-28 19:24:09 +02:00
|
|
|
self.place_read_ports()
|
2018-09-07 02:59:21 +02:00
|
|
|
self.route_read_wordlines()
|
|
|
|
|
self.route_read_bitlines()
|
|
|
|
|
self.route_read_access()
|
2018-08-28 19:24:09 +02:00
|
|
|
self.extend_well()
|
|
|
|
|
|
2018-09-14 01:53:24 +02:00
|
|
|
if self.replica_bitcell:
|
|
|
|
|
self.route_rbc_short()
|
|
|
|
|
|
2018-09-12 10:53:41 +02:00
|
|
|
# in netlist_only mode, calling offset_all_coordinates 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()
|
2018-08-28 19:24:09 +02:00
|
|
|
self.DRC_LVS()
|
|
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
def add_pins(self):
|
2018-09-07 02:59:21 +02:00
|
|
|
self.rw_bl_names = []
|
|
|
|
|
self.rw_br_names = []
|
|
|
|
|
self.w_bl_names = []
|
|
|
|
|
self.w_br_names = []
|
|
|
|
|
self.r_bl_names = []
|
|
|
|
|
self.r_br_names = []
|
2018-09-07 04:36:50 +02:00
|
|
|
self.rw_wl_names = []
|
|
|
|
|
self.w_wl_names = []
|
|
|
|
|
self.r_wl_names = []
|
2018-09-07 02:59:21 +02:00
|
|
|
port = 0
|
|
|
|
|
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_rw_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
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
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_w_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
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
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_r_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
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
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_rw_ports):
|
2018-09-07 04:36:50 +02:00
|
|
|
self.add_pin("wl{}".format(port))
|
|
|
|
|
self.rw_wl_names.append("wl{}".format(port))
|
|
|
|
|
port += 1
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_w_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_pin("wl{}".format(port))
|
2018-09-07 04:36:50 +02:00
|
|
|
self.w_wl_names.append("wl{}".format(port))
|
|
|
|
|
port += 1
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(self.num_r_ports):
|
2018-09-07 04:36:50 +02:00
|
|
|
self.add_pin("wl{}".format(port))
|
|
|
|
|
self.r_wl_names.append("wl{}".format(port))
|
|
|
|
|
port += 1
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
self.add_pin("vdd")
|
|
|
|
|
self.add_pin("gnd")
|
2018-09-07 04:36:50 +02:00
|
|
|
|
2018-09-14 01:53:24 +02:00
|
|
|
if self.replica_bitcell:
|
|
|
|
|
self.Q_bar = "vdd"
|
|
|
|
|
else:
|
|
|
|
|
self.Q_bar = "Q_bar"
|
|
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def add_modules(self):
|
2018-09-07 02:59:21 +02:00
|
|
|
"""
|
|
|
|
|
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
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_rw_ports > 0):
|
|
|
|
|
inverter_nmos_width = self.num_rw_ports*3*parameter["min_tx_size"]
|
2018-08-07 18:44:01 +02:00
|
|
|
inverter_pmos_width = parameter["min_tx_size"]
|
|
|
|
|
readwrite_nmos_width = 1.5*parameter["min_tx_size"]
|
|
|
|
|
write_nmos_width = parameter["min_tx_size"]
|
|
|
|
|
read_nmos_width = 2*parameter["min_tx_size"]
|
|
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
# if there are no read/write ports, then the inverter nmos is statically sized for the dual port case
|
2018-08-07 18:44:01 +02:00
|
|
|
else:
|
|
|
|
|
inverter_nmos_width = 2*parameter["min_tx_size"]
|
|
|
|
|
inverter_pmos_width = parameter["min_tx_size"]
|
|
|
|
|
readwrite_nmos_width = 1.5*parameter["min_tx_size"]
|
|
|
|
|
write_nmos_width = parameter["min_tx_size"]
|
|
|
|
|
read_nmos_width = 2*parameter["min_tx_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):
|
2018-08-28 19:24:09 +02:00
|
|
|
""" Calculate transistor spacings """
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# calculate metal contact extensions over transistor active
|
2018-08-07 18:44:01 +02:00
|
|
|
self.inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height)
|
|
|
|
|
self.readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height)
|
|
|
|
|
self.write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height)
|
|
|
|
|
self.read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height)
|
|
|
|
|
|
|
|
|
|
# calculate the distance threshold for different gate contact spacings
|
|
|
|
|
self.gate_contact_thres = drc["poly_to_active"] - drc["minwidth_metal2"]
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
#calculations for horizontal transistor to tansistor spacing
|
2018-08-07 18:44:01 +02:00
|
|
|
# inverter spacings
|
|
|
|
|
self.inverter_to_inverter_spacing = contact.poly.height + drc["minwidth_metal1"]
|
|
|
|
|
self.inverter_to_write_spacing = drc["pwell_to_nwell"] + 2*drc["well_enclosure_active"]
|
|
|
|
|
|
|
|
|
|
# readwrite to readwrite transistor spacing (also acts as readwrite to write transistor spacing)
|
|
|
|
|
if(self.readwrite_nmos_contact_extension > self.gate_contact_thres):
|
2018-08-30 00:30:50 +02:00
|
|
|
self.readwrite_to_readwrite_spacing = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"]
|
2018-08-07 18:44:01 +02:00
|
|
|
else:
|
2018-08-30 00:30:50 +02:00
|
|
|
self.readwrite_to_readwrite_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"]
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# write to write transistor spacing
|
|
|
|
|
if(self.write_nmos_contact_extension > self.gate_contact_thres):
|
2018-08-30 00:30:50 +02:00
|
|
|
self.write_to_write_spacing = drc["minwidth_metal2"] + self.write_nmos_contact_extension + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"]
|
2018-08-07 18:44:01 +02:00
|
|
|
else:
|
2018-08-30 00:30:50 +02:00
|
|
|
self.write_to_write_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"]
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# read to read transistor spacing
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
self.read_to_read_spacing = 2*(drc["minwidth_metal2"] + self.read_nmos_contact_extension) + drc["minwidth_metal1"] + 2*contact.poly.width
|
|
|
|
|
else:
|
|
|
|
|
self.read_to_read_spacing = 2*drc["poly_to_active"] + drc["minwidth_metal1"] + 2*contact.poly.width
|
|
|
|
|
|
|
|
|
|
# write to read transistor spacing (also acts as readwrite to read transistor spacing)
|
|
|
|
|
# calculation is dependent on whether the read transistor is adjacent to a write transistor or a readwrite transistor
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_w_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
if(self.write_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
write_portion = drc["minwidth_metal2"] + self.write_nmos_contact_extension
|
|
|
|
|
else:
|
|
|
|
|
write_portion = drc["poly_to_active"]
|
|
|
|
|
else:
|
|
|
|
|
if(self.readwrite_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
write_portion = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension
|
|
|
|
|
else:
|
|
|
|
|
write_portion = drc["poly_to_active"]
|
|
|
|
|
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
read_portion = drc["minwidth_metal2"] + self.read_nmos_contact_extension
|
|
|
|
|
else:
|
|
|
|
|
read_portion = drc["poly_to_active"]
|
|
|
|
|
|
2018-08-30 00:30:50 +02:00
|
|
|
self.write_to_read_spacing = write_portion + read_portion + 2*contact.poly.width + drc["poly_to_polycontact"]
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
# calculations for transistor tiling (transistor + spacing)
|
2018-08-07 18:44:01 +02:00
|
|
|
self.inverter_tile_width = self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing
|
|
|
|
|
self.readwrite_tile_width = self.readwrite_to_readwrite_spacing + self.readwrite_nmos.active_height
|
|
|
|
|
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
|
|
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
# calculation for row line tiling
|
|
|
|
|
self.rail_tile_height = drc["active_to_body_active"] + contact.well.width
|
2018-09-14 01:53:24 +02:00
|
|
|
if self.inverter_pmos_contact_extension > 0:
|
|
|
|
|
self.vdd_tile_height = self.inverter_pmos_contact_extension + drc["minwidth_metal1"] + contact.well.width
|
|
|
|
|
else:
|
|
|
|
|
self.vdd_tile_height = self.rail_tile_height
|
2018-08-07 18:44:01 +02:00
|
|
|
self.rowline_tile_height = drc["minwidth_metal1"] + contact.m1m2.width
|
|
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
# calculations related to inverter connections
|
2018-08-30 00:30:50 +02:00
|
|
|
self.inverter_gap = drc["poly_to_active"] + drc["poly_to_polycontact"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.inverter_pmos_contact_extension
|
2018-08-07 18:44:01 +02:00
|
|
|
self.cross_couple_lower_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width
|
2018-08-30 00:30:50 +02:00
|
|
|
self.cross_couple_upper_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + drc["poly_to_polycontact"] + 1.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_postions(self):
|
|
|
|
|
"""
|
2018-09-07 02:59:21 +02:00
|
|
|
Calculate positions that describe the edges and dimensions of the cell
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
|
|
|
|
# create flags for excluding readwrite, write, or read port calculations if they are not included in the bitcell
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_rw_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
self.readwrite_port_flag = True
|
|
|
|
|
else:
|
|
|
|
|
self.readwrite_port_flag = False
|
|
|
|
|
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_w_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
self.write_port_flag = True
|
|
|
|
|
else:
|
|
|
|
|
self.write_port_flag = False
|
|
|
|
|
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_r_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
self.read_port_flag = True
|
|
|
|
|
else:
|
|
|
|
|
self.read_port_flag = False
|
|
|
|
|
|
|
|
|
|
# determine the distance of the leftmost/rightmost transistor gate connection
|
2018-08-31 21:03:28 +02:00
|
|
|
if (self.num_r_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
end_connection = drc["minwidth_metal2"] + self.read_nmos_contact_extension + contact.m1m2.height
|
|
|
|
|
else:
|
|
|
|
|
end_connection = drc["poly_to_active"] + contact.m1m2.height
|
|
|
|
|
else:
|
|
|
|
|
if(self.readwrite_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
end_connection = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.m1m2.height
|
|
|
|
|
else:
|
|
|
|
|
end_connection = drc["poly_to_active"] + contact.m1m2.height
|
|
|
|
|
|
|
|
|
|
# leftmost position = storage width + read/write ports width + write ports width + read ports width + end transistor gate connections + metal spacing necessary for tiling the bitcell
|
|
|
|
|
self.leftmost_xpos = -self.inverter_tile_width \
|
|
|
|
|
- self.inverter_to_write_spacing \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.readwrite_port_flag*(self.readwrite_nmos.active_height + (self.num_rw_ports-1)*self.readwrite_tile_width) \
|
2018-08-07 18:44:01 +02:00
|
|
|
- self.write_port_flag*self.readwrite_port_flag*self.write_to_write_spacing \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.write_port_flag*(self.write_nmos.active_height + (self.num_w_ports-1)*self.write_tile_width) \
|
2018-08-07 18:44:01 +02:00
|
|
|
- self.read_port_flag*self.write_to_read_spacing \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.read_port_flag*(self.read_nmos.active_height + (self.num_r_ports-1)*self.read_tile_width) \
|
2018-08-07 18:44:01 +02:00
|
|
|
- end_connection \
|
2018-08-30 00:54:49 +02:00
|
|
|
- 0.5*drc["poly_to_polycontact"]
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
self.rightmost_xpos = -self.leftmost_xpos
|
|
|
|
|
|
|
|
|
|
# bottommost position = gnd height + rwwl height + wwl height + rwl height + space needed between tiled bitcells
|
|
|
|
|
array_tiling_offset = 0.5*drc["minwidth_metal2"]
|
|
|
|
|
self.botmost_ypos = -self.rail_tile_height \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.num_rw_ports*self.rowline_tile_height \
|
|
|
|
|
- self.num_w_ports*self.rowline_tile_height \
|
|
|
|
|
- self.num_r_ports*self.rowline_tile_height \
|
2018-08-07 18:44:01 +02:00
|
|
|
- array_tiling_offset
|
|
|
|
|
|
|
|
|
|
# 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 \
|
2018-09-14 01:53:24 +02:00
|
|
|
+ self.vdd_tile_height
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# calculations for the cell dimensions
|
2018-08-30 00:54:49 +02:00
|
|
|
array_vdd_overlap = 0.5*contact.well.width
|
2018-08-07 18:44:01 +02:00
|
|
|
self.width = -2*self.leftmost_xpos
|
|
|
|
|
self.height = self.topmost_ypos - self.botmost_ypos - array_vdd_overlap
|
|
|
|
|
|
2018-08-29 17:52:45 +02:00
|
|
|
|
|
|
|
|
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)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst([self.Q_bar, "Q", "gnd", "gnd"])
|
2018-08-29 17:52:45 +02:00
|
|
|
|
|
|
|
|
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
|
|
|
|
|
mod=self.inverter_nmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst(["gnd", self.Q_bar, "Q", "gnd"])
|
2018-08-29 17:52:45 +02:00
|
|
|
|
|
|
|
|
# create active for pmos
|
|
|
|
|
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
|
|
|
|
|
mod=self.inverter_pmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst([self.Q_bar, "Q", "vdd", "vdd"])
|
2018-08-29 17:52:45 +02:00
|
|
|
|
|
|
|
|
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
|
|
|
|
|
mod=self.inverter_pmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst(["vdd", self.Q_bar, "Q", "vdd"])
|
2018-08-29 17:52:45 +02:00
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def place_storage(self):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-09-07 02:59:21 +02:00
|
|
|
Places the transistors for the crossed coupled inverters in the bitcell
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# 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
|
2018-08-28 19:24:09 +02:00
|
|
|
self.inverter_nmos_left.place([left_inverter_xpos,0])
|
|
|
|
|
self.inverter_nmos_right.place([right_inverter_xpos,0])
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# create active for pmos
|
2018-08-28 19:24:09 +02:00
|
|
|
self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos])
|
|
|
|
|
self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos])
|
|
|
|
|
|
2018-09-13 10:42:06 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def route_storage(self):
|
2018-09-07 02:59:21 +02:00
|
|
|
"""
|
|
|
|
|
Routes inputs and outputs of inverters to cross couple them
|
|
|
|
|
"""
|
2018-08-07 18:44:01 +02:00
|
|
|
# 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])
|
|
|
|
|
|
|
|
|
|
# update furthest left and right transistor edges (this will propagate to further transistor offset calculations)
|
|
|
|
|
self.left_building_edge = -self.inverter_tile_width
|
|
|
|
|
self.right_building_edge = self.inverter_tile_width
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def route_rails(self):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-09-07 02:59:21 +02:00
|
|
|
Adds gnd and vdd rails and connects them to the inverters
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add rails for vdd and gnd
|
2018-08-07 18:44:01 +02:00
|
|
|
self.gnd_position = vector(self.leftmost_xpos, -self.rail_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)
|
|
|
|
|
|
|
|
|
|
vdd_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \
|
2018-09-14 01:53:24 +02:00
|
|
|
+ self.vdd_tile_height - contact.well.second_layer_width
|
2018-08-07 18:44:01 +02:00
|
|
|
self.vdd_position = vector(self.leftmost_xpos, vdd_ypos)
|
|
|
|
|
self.vdd = self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="metal1",
|
|
|
|
|
offset=self.vdd_position,
|
|
|
|
|
width=self.width,
|
2018-09-13 10:42:06 +02:00
|
|
|
height=contact.well.second_layer_width)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Connect inverters to rails
|
2018-08-07 18:44:01 +02:00
|
|
|
# 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])
|
|
|
|
|
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def create_readwrite_ports(self):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-08-28 19:24:09 +02:00
|
|
|
Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell.
|
2018-08-07 18:44:01 +02:00
|
|
|
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 RWBL_bar to Q_bar.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# define write transistor variables as empty arrays based on the number of write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
self.readwrite_nmos_left = [None] * self.num_rw_ports
|
|
|
|
|
self.readwrite_nmos_right = [None] * self.num_rw_ports
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
# iterate over the number of read/write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_rw_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# add read/write transistors
|
|
|
|
|
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
|
|
|
|
|
mod=self.readwrite_nmos)
|
2018-09-07 04:36:50 +02:00
|
|
|
self.connect_inst(["Q", self.rw_wl_names[k], self.rw_bl_names[k], "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
|
|
|
|
|
mod=self.readwrite_nmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def place_readwrite_ports(self):
|
|
|
|
|
"""
|
|
|
|
|
Places read/write ports in the bit cell.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Define variables relevant to write transistors
|
2018-08-31 21:03:28 +02:00
|
|
|
self.rwwl_positions = [None] * self.num_rw_ports
|
|
|
|
|
self.rwbl_positions = [None] * self.num_rw_ports
|
|
|
|
|
self.rwbl_bar_positions = [None] * self.num_rw_ports
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# define offset correction due to rotation of the ptx module
|
|
|
|
|
readwrite_rotation_correct = self.readwrite_nmos.active_height
|
|
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
# iterate over the number of read/write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_rw_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add transistors
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate read/write transistor offsets
|
|
|
|
|
left_readwrite_transistor_xpos = self.left_building_edge \
|
|
|
|
|
- self.inverter_to_write_spacing \
|
|
|
|
|
- self.readwrite_nmos.active_height - k*self.readwrite_tile_width \
|
|
|
|
|
+ readwrite_rotation_correct
|
|
|
|
|
|
|
|
|
|
right_readwrite_transistor_xpos = self.right_building_edge \
|
|
|
|
|
+ self.inverter_to_write_spacing \
|
|
|
|
|
+ k*self.readwrite_tile_width \
|
|
|
|
|
+ readwrite_rotation_correct
|
|
|
|
|
|
|
|
|
|
# add read/write transistors
|
2018-08-28 19:24:09 +02:00
|
|
|
self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add RWWL lines
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate RWWL position
|
|
|
|
|
rwwl_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height
|
|
|
|
|
self.rwwl_positions[k] = vector(self.leftmost_xpos, rwwl_ypos)
|
|
|
|
|
|
|
|
|
|
# add pin for RWWL
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.rw_wl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal1",
|
|
|
|
|
offset=self.rwwl_positions[k],
|
|
|
|
|
width=self.width,
|
|
|
|
|
height=contact.m1m2.width)
|
2018-09-07 02:59:21 +02:00
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
# add pins for RWBL and RWBL_bar, overlaid on source contacts
|
|
|
|
|
self.rwbl_positions[k] = vector(self.readwrite_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.rw_bl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal2",
|
|
|
|
|
offset=self.rwbl_positions[k],
|
|
|
|
|
width=drc["minwidth_metal2"],
|
|
|
|
|
height=self.height)
|
|
|
|
|
|
|
|
|
|
self.rwbl_bar_positions[k] = vector(self.readwrite_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.rw_br_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal2",
|
|
|
|
|
offset=self.rwbl_bar_positions[k],
|
|
|
|
|
width=drc["minwidth_metal2"],
|
|
|
|
|
height=self.height)
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
# update furthest left and right transistor edges
|
|
|
|
|
self.left_building_edge = left_readwrite_transistor_xpos - self.readwrite_nmos.active_height
|
|
|
|
|
self.right_building_edge = right_readwrite_transistor_xpos
|
|
|
|
|
|
|
|
|
|
def route_readwrite_wordlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read/write trnasistors to their respective wordlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_rw_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Gate/RWWL connections
|
2018-08-07 18:44:01 +02:00
|
|
|
# add poly-to-meltal2 contacts to connect gate of read/write transistors to RWWL (contact next to gate)
|
|
|
|
|
# contact must be placed a metal1 width below the source pin to avoid drc from source pin routings
|
|
|
|
|
if(self.readwrite_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.readwrite_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.readwrite_nmos_left[k].offset.x - self.readwrite_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
contact_ypos = self.readwrite_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)
|
|
|
|
|
|
|
|
|
|
if(self.readwrite_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.readwrite_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.readwrite_nmos_right[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
contact_ypos = self.readwrite_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 read/write transistor to contact (poly path)
|
|
|
|
|
midL = vector(left_gate_contact.x, self.readwrite_nmos_left[k].get_pin("G").lc().y)
|
|
|
|
|
self.add_path("poly", [self.readwrite_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width)
|
|
|
|
|
|
|
|
|
|
midR = vector(right_gate_contact.x, self.readwrite_nmos_right[k].get_pin("G").rc().y)
|
|
|
|
|
self.add_path("poly", [self.readwrite_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width)
|
|
|
|
|
|
|
|
|
|
# add metal1-to-metal2 contacts to RWWL lines
|
|
|
|
|
left_rwwl_contact = vector(left_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=left_rwwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
right_rwwl_contact = vector(right_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=right_rwwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
# connect read/write transistor gate contacts to RWWL contacts (metal2 path)
|
|
|
|
|
self.add_path("metal2", [left_gate_contact, left_rwwl_contact])
|
|
|
|
|
self.add_path("metal2", [right_gate_contact, right_rwwl_contact])
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
def route_readwrite_bitlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read/write transistors to their respective bitlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_rw_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
# Source/RWBL/RWBL_bar connections
|
|
|
|
|
# add metal1-to-metal2 contacts on top of read/write transistor source pins for connection to WBL and WBL_bar
|
|
|
|
|
offset_left = self.readwrite_nmos_left[k].get_pin("S").center()
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=offset_left,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
offset_right = self.readwrite_nmos_right[k].get_pin("S").center()
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=offset_right,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def route_readwrite_access(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read/write transistors to the storage component of the bitcell
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
last_inst = self.num_rw_ports - 1
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
# Drain/Storage connections
|
|
|
|
|
# this path only needs to be drawn once on the last iteration of the loop
|
|
|
|
|
# add contacts to connect gate of inverters to drain of read/write transistors
|
|
|
|
|
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_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_polycontact"] + 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 read/write transistors (metal1 path)
|
|
|
|
|
midL0 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], left_storage_contact.y)
|
|
|
|
|
midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.readwrite_nmos_left[last_inst].get_pin("D").lc().y)
|
|
|
|
|
self.add_path("metal1", [left_storage_contact, midL0], width=contact.poly.second_layer_width) # width needed to avoid drc error
|
|
|
|
|
self.add_path("metal1", [midL0+vector(0,0.5*contact.poly.second_layer_width), midL1, self.readwrite_nmos_left[last_inst].get_pin("D").lc()])
|
|
|
|
|
|
|
|
|
|
midR0 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], right_storage_contact.y)
|
|
|
|
|
midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.readwrite_nmos_right[last_inst].get_pin("D").rc().y)
|
|
|
|
|
self.add_path("metal1", [right_storage_contact, midR0], width=contact.poly.second_layer_width)
|
|
|
|
|
self.add_path("metal1", [midR0+vector(0,0.5*contact.poly.second_layer_width), midR1, self.readwrite_nmos_right[last_inst].get_pin("D").rc()])
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def create_write_ports(self):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-08-28 19:24:09 +02:00
|
|
|
Creates write ports in the bit cell. A differential pair of transistors can write only.
|
2018-08-07 18:44:01 +02:00
|
|
|
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 WBL_bar to Q_bar.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Define variables relevant to write transistors
|
2018-08-07 18:44:01 +02:00
|
|
|
# define offset correction due to rotation of the ptx module
|
|
|
|
|
write_rotation_correct = self.write_nmos.active_height
|
|
|
|
|
|
|
|
|
|
# define write transistor variables as empty arrays based on the number of write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
self.write_nmos_left = [None] * self.num_w_ports
|
|
|
|
|
self.write_nmos_right = [None] * self.num_w_ports
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
# iterate over the number of write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_w_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# add write transistors
|
|
|
|
|
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
|
|
|
|
|
mod=self.write_nmos)
|
2018-09-07 04:36:50 +02:00
|
|
|
self.connect_inst(["Q", self.w_wl_names[k], self.w_bl_names[k], "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
|
|
|
|
|
mod=self.write_nmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def place_write_ports(self):
|
|
|
|
|
"""
|
|
|
|
|
Places write ports in the bit cell.
|
|
|
|
|
"""
|
|
|
|
|
# Define variables relevant to write transistors
|
2018-08-31 21:03:28 +02:00
|
|
|
self.wwl_positions = [None] * self.num_w_ports
|
|
|
|
|
self.wbl_positions = [None] * self.num_w_ports
|
|
|
|
|
self.wbl_bar_positions = [None] * self.num_w_ports
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
# define offset correction due to rotation of the ptx module
|
|
|
|
|
write_rotation_correct = self.write_nmos.active_height
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# iterate over the number of write ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_w_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add transistors
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate write transistor offsets
|
|
|
|
|
left_write_transistor_xpos = self.left_building_edge \
|
|
|
|
|
- (not self.readwrite_port_flag)*self.inverter_to_write_spacing \
|
|
|
|
|
- (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \
|
|
|
|
|
- self.write_nmos.active_height - k*self.write_tile_width \
|
|
|
|
|
+ write_rotation_correct
|
|
|
|
|
|
|
|
|
|
right_write_transistor_xpos = self.right_building_edge \
|
|
|
|
|
+ (not self.readwrite_port_flag)*self.inverter_to_write_spacing \
|
|
|
|
|
+ (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \
|
|
|
|
|
+ k*self.write_tile_width \
|
|
|
|
|
+ write_rotation_correct
|
|
|
|
|
|
|
|
|
|
# add write transistors
|
2018-08-28 19:24:09 +02:00
|
|
|
self.write_nmos_left[k].place(offset=[left_write_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
self.write_nmos_right[k].place(offset=[right_write_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add WWL lines
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate WWL position
|
|
|
|
|
wwl_ypos = self.gnd_position.y \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.num_rw_ports*self.rowline_tile_height \
|
2018-08-07 18:44:01 +02:00
|
|
|
- (k+1)*self.rowline_tile_height
|
|
|
|
|
self.wwl_positions[k] = vector(self.leftmost_xpos, wwl_ypos)
|
|
|
|
|
|
|
|
|
|
# add pin for WWL
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.w_wl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal1",
|
|
|
|
|
offset=self.wwl_positions[k],
|
|
|
|
|
width=self.width,
|
|
|
|
|
height=contact.m1m2.width)
|
|
|
|
|
|
|
|
|
|
# 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)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.w_bl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.w_br_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal2",
|
|
|
|
|
offset=self.wbl_bar_positions[k],
|
|
|
|
|
width=drc["minwidth_metal2"],
|
|
|
|
|
height=self.height)
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
# update furthest left and right transistor edges
|
|
|
|
|
self.left_building_edge = left_write_transistor_xpos - self.write_nmos.active_height
|
|
|
|
|
self.right_building_edge = right_write_transistor_xpos
|
|
|
|
|
|
|
|
|
|
def route_write_wordlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes write transistors to their respective wordlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_w_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Gate/WWL connections
|
2018-08-07 18:44:01 +02:00
|
|
|
# add poly-to-meltal2 contacts to connect gate of write transistors to WWL (contact next to gate)
|
|
|
|
|
# contact must be placed a metal width below the source pin to avoid drc from source pin routings
|
|
|
|
|
if(self.write_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.write_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.write_nmos_left[k].offset.x - self.write_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
if(self.write_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.write_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.write_nmos_right[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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 WWL lines
|
|
|
|
|
left_wwl_contact = vector(left_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=left_wwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
right_wwl_contact = vector(right_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=right_wwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
# connect write transistor gate contacts to WWL contacts (metal2 path)
|
|
|
|
|
self.add_path("metal2", [left_gate_contact, left_wwl_contact])
|
|
|
|
|
self.add_path("metal2", [right_gate_contact, right_wwl_contact])
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
def route_write_bitlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes write transistors to their respective bitlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_w_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
# 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)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
def route_write_access(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes write transistors to the storage component of the bitcell
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
last_inst = self.num_w_ports - 1
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
# Drain/Storage connections
|
|
|
|
|
# this path only needs to be drawn once on the last iteration of the loop
|
|
|
|
|
# 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_polycontact"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos)
|
|
|
|
|
self.add_contact_center(layers=("poly", "contact", "metal1"),
|
|
|
|
|
offset=left_storage_contact,
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
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_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(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], left_storage_contact.y)
|
|
|
|
|
midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.write_nmos_left[last_inst].get_pin("D").lc().y)
|
|
|
|
|
self.add_path("metal1", [left_storage_contact, midL0], width=contact.poly.second_layer_width) # width needed to avoid drc error
|
|
|
|
|
self.add_path("metal1", [midL0+vector(0,0.5*contact.poly.second_layer_width), midL1, self.write_nmos_left[last_inst].get_pin("D").lc()])
|
|
|
|
|
|
|
|
|
|
midR0 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], right_storage_contact.y)
|
|
|
|
|
midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.write_nmos_right[last_inst].get_pin("D").rc().y)
|
|
|
|
|
self.add_path("metal1", [right_storage_contact, midR0], width=contact.poly.second_layer_width)
|
|
|
|
|
self.add_path("metal1", [midR0+vector(0,0.5*contact.poly.second_layer_width), midR1, self.write_nmos_right[last_inst].get_pin("D").rc()])
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
def create_read_ports(self):
|
2018-08-07 18:44:01 +02:00
|
|
|
"""
|
2018-08-28 19:24:09 +02:00
|
|
|
Creates read ports in the bit cell. A differential pair of ports can read only.
|
2018-08-07 18:44:01 +02:00
|
|
|
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.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# define read transistor variables as empty arrays based on the number of read ports
|
2018-08-31 21:03:28 +02:00
|
|
|
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
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
# iterate over the number of read ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_r_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# add read-access transistors
|
|
|
|
|
self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k),
|
|
|
|
|
mod=self.read_nmos)
|
2018-09-14 01:53:24 +02:00
|
|
|
self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
|
|
|
|
|
mod=self.read_nmos)
|
|
|
|
|
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{}".format(k),
|
|
|
|
|
mod=self.read_nmos)
|
2018-09-07 04:36:50 +02:00
|
|
|
self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k),
|
|
|
|
|
mod=self.read_nmos)
|
2018-09-07 04:36:50 +02:00
|
|
|
self.connect_inst([self.r_br_names[k], self.r_wl_names[k], "RA_to_R_right{}".format(k), "gnd"])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
|
|
|
|
def place_read_ports(self):
|
|
|
|
|
"""
|
|
|
|
|
Places the read ports in the bit cell.
|
|
|
|
|
"""
|
|
|
|
|
# Define variables relevant to read transistors
|
2018-08-31 21:03:28 +02:00
|
|
|
self.rwl_positions = [None] * self.num_r_ports
|
|
|
|
|
self.rbl_positions = [None] * self.num_r_ports
|
|
|
|
|
self.rbl_bar_positions = [None] * self.num_r_ports
|
2018-08-28 19:24:09 +02:00
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
# define offset correction due to rotation of the ptx module
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
# iterate over the number of read ports
|
2018-08-31 21:03:28 +02:00
|
|
|
for k in range(0,self.num_r_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add transistors
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate transistor offsets
|
|
|
|
|
left_read_transistor_xpos = self.left_building_edge \
|
|
|
|
|
- self.write_to_read_spacing \
|
|
|
|
|
- self.read_nmos.active_height - k*self.read_tile_width \
|
|
|
|
|
+ read_rotation_correct
|
|
|
|
|
|
|
|
|
|
right_read_transistor_xpos = self.right_building_edge \
|
|
|
|
|
+ self.write_to_read_spacing \
|
|
|
|
|
+ k*self.read_tile_width \
|
|
|
|
|
+ read_rotation_correct
|
|
|
|
|
|
|
|
|
|
# add read-access transistors
|
2018-08-29 17:52:45 +02:00
|
|
|
self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-29 17:52:45 +02:00
|
|
|
self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos,0],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# add read transistors
|
2018-08-29 17:52:45 +02:00
|
|
|
self.read_nmos_left[k].place(offset=[left_read_transistor_xpos,overlap_offset.x],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-29 17:52:45 +02:00
|
|
|
self.read_nmos_right[k].place(offset=[right_read_transistor_xpos,overlap_offset.x],
|
|
|
|
|
rotate=90)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Add RWL lines
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate RWL position
|
|
|
|
|
rwl_ypos = self.gnd_position.y \
|
2018-08-31 21:03:28 +02:00
|
|
|
- self.num_rw_ports*self.rowline_tile_height \
|
|
|
|
|
- self.num_w_ports*self.rowline_tile_height \
|
2018-08-07 18:44:01 +02:00
|
|
|
- (k+1)*self.rowline_tile_height
|
|
|
|
|
self.rwl_positions[k] = vector(self.leftmost_xpos, rwl_ypos)
|
|
|
|
|
|
|
|
|
|
# add pin for RWL
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.r_wl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal1",
|
|
|
|
|
offset=self.rwl_positions[k],
|
|
|
|
|
width=self.width,
|
|
|
|
|
height=contact.m1m2.width)
|
|
|
|
|
|
|
|
|
|
# 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)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.r_bl_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
2018-09-07 02:59:21 +02:00
|
|
|
self.add_layout_pin(text=self.r_br_names[k],
|
2018-08-07 18:44:01 +02:00
|
|
|
layer="metal2",
|
|
|
|
|
offset=self.rbl_bar_positions[k],
|
|
|
|
|
width=drc["minwidth_metal2"],
|
|
|
|
|
height=self.height)
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
def route_read_wordlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read transistors to their respective worlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_r_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Gate of read transistor / RWL connection
|
2018-08-07 18:44:01 +02:00
|
|
|
# add poly-to-meltal2 contacts to connect gate of read transistors to RWL (contact next to gate)
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.read_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.read_nmos_left[k].offset.x - self.read_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.read_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.read_nmos_right[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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 RWL lines
|
|
|
|
|
left_rwl_contact = vector(left_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=left_rwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
right_rwl_contact = vector(right_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width)
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=right_rwl_contact,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
# connect read transistor gate contacts to RWL contacts (metal2 path)
|
|
|
|
|
self.add_path("metal2", [left_gate_contact, left_rwl_contact])
|
|
|
|
|
self.add_path("metal2", [right_gate_contact, right_rwl_contact])
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# Source of read-access transistor / GND connection
|
2018-08-07 18:44:01 +02:00
|
|
|
# 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])
|
2018-09-07 02:59:21 +02:00
|
|
|
|
|
|
|
|
def route_read_bitlines(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read transistors to their respective bitlines
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_r_ports):
|
2018-09-07 02:59:21 +02:00
|
|
|
# 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)
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-09-07 02:59:21 +02:00
|
|
|
offset_right = self.read_nmos_right[k].get_pin("D").center()
|
|
|
|
|
self.add_contact_center(layers=("metal1", "via1", "metal2"),
|
|
|
|
|
offset=offset_right,
|
|
|
|
|
rotate=90)
|
|
|
|
|
|
|
|
|
|
def route_read_access(self):
|
|
|
|
|
"""
|
|
|
|
|
Routes read access transistors to the storage component of the bitcell
|
|
|
|
|
"""
|
2018-09-07 04:36:50 +02:00
|
|
|
for k in range(0,self.num_r_ports):
|
2018-08-28 19:24:09 +02:00
|
|
|
# Gate of read-access transistor / storage connection
|
2018-08-07 18:44:01 +02:00
|
|
|
# add poly-to-metal1 contacts to connect gate of read-access transistors to output of inverters (contact next to gate)
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.read_nmos_left[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.read_nmos_left[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
if(self.read_nmos_contact_extension > self.gate_contact_thres):
|
|
|
|
|
contact_xpos = self.read_nmos_right[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width
|
|
|
|
|
else:
|
2018-09-07 04:36:50 +02:00
|
|
|
contact_xpos = self.read_nmos_right[k].offset.x - self.read_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width
|
2018-08-07 18:44:01 +02:00
|
|
|
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])
|
|
|
|
|
|
|
|
|
|
# save the positions of the first gate contacts for use in later iterations
|
|
|
|
|
if(k == 0):
|
|
|
|
|
left_gate_contact0 = left_gate_contact
|
|
|
|
|
right_gate_contact0 = 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 first read access gate contact
|
|
|
|
|
# mid2: route up or 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(left_gate_contact0.x, self.read_nmos_left[0].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
|
|
|
midL2 = vector(left_gate_contact0.x, 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(right_gate_contact0.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"])
|
|
|
|
|
midR2 = vector(right_gate_contact0.x, 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])
|
2018-08-28 19:24:09 +02:00
|
|
|
|
2018-08-07 18:44:01 +02:00
|
|
|
def extend_well(self):
|
|
|
|
|
"""
|
|
|
|
|
Connects wells between ptx modules to avoid drc spacing issues.
|
|
|
|
|
Since the pwell of the read ports rise higher than the nwell of the inverters,
|
|
|
|
|
the well connections must be done piecewise to avoid pwell and nwell overlap.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# extend pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well
|
2018-08-07 18:44:01 +02:00
|
|
|
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)
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# extend pwell over read/write and write transistors to the
|
|
|
|
|
# height of the write transistor well (read/write and write
|
|
|
|
|
# transistors are the same height)
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_w_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate the edge of the write transistor well closest to the center
|
|
|
|
|
left_write_well_xpos = self.write_nmos_left[0].offset.x + drc["well_enclosure_active"]
|
|
|
|
|
right_write_well_xpos = self.write_nmos_right[0].offset.x - self.write_nmos.active_height - drc["well_enclosure_active"]
|
|
|
|
|
else:
|
|
|
|
|
# calculate the edge of the read/write transistor well closest to the center
|
|
|
|
|
left_write_well_xpos = self.readwrite_nmos_left[0].offset.x + drc["well_enclosure_active"]
|
|
|
|
|
right_write_well_xpos = self.readwrite_nmos_right[0].offset.x - self.readwrite_nmos.active_height - 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)
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# extend pwell over the read transistors to the height of the bitcell
|
2018-08-31 21:03:28 +02:00
|
|
|
if(self.num_r_ports > 0):
|
2018-08-07 18:44:01 +02:00
|
|
|
# calculate the edge of the read transistor well clostest to the center
|
|
|
|
|
left_read_well_xpos = self.read_nmos_left[0].offset.x + drc["well_enclosure_active"]
|
|
|
|
|
right_read_well_xpos = self.read_nmos_right[0].offset.x - self.read_nmos.active_height - 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)
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# extend nwell to encompass inverter_pmos
|
2018-08-07 18:44:01 +02:00
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
2018-08-28 19:24:09 +02:00
|
|
|
# add well contacts
|
2018-08-07 18:44:01 +02:00
|
|
|
# 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,
|
2018-08-30 00:30:50 +02:00
|
|
|
rotate=90,
|
|
|
|
|
implant_type="p",
|
|
|
|
|
well_type="p")
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
# connect nimplants to vdd
|
2018-09-13 10:42:06 +02:00
|
|
|
offset = vector(0, self.vdd_position.y + 0.5*contact.well.second_layer_width)
|
2018-08-07 18:44:01 +02:00
|
|
|
self.add_contact_center(layers=("active", "contact", "metal1"),
|
|
|
|
|
offset=offset,
|
2018-08-30 00:30:50 +02:00
|
|
|
rotate=90,
|
|
|
|
|
implant_type="n",
|
|
|
|
|
well_type="n")
|
2018-08-07 18:44:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = []
|
2018-09-07 02:59:21 +02:00
|
|
|
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))
|
2018-08-07 18:44:01 +02:00
|
|
|
bitcell_pins.append("vdd")
|
|
|
|
|
bitcell_pins.append("gnd")
|
|
|
|
|
return bitcell_pins
|
|
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_all_wl_names(self):
|
|
|
|
|
""" Creates a list of all wordline pin names """
|
2018-09-07 04:36:50 +02:00
|
|
|
wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names
|
|
|
|
|
return wordline_names
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_all_bitline_names(self):
|
|
|
|
|
""" Creates a list of all bitline pin names (both bl and br) """
|
2018-09-07 02:59:21 +02:00
|
|
|
bitline_pins = []
|
|
|
|
|
for port in range(self.total_ports):
|
2018-09-07 04:36:50 +02:00
|
|
|
bitline_pins.append("bl{0}".format(port))
|
|
|
|
|
bitline_pins.append("br{0}".format(port))
|
2018-09-07 02:59:21 +02:00
|
|
|
return bitline_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_all_bl_names(self):
|
|
|
|
|
""" Creates a list of all bl pins names """
|
2018-09-07 04:36:50 +02:00
|
|
|
bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names
|
2018-09-07 02:59:21 +02:00
|
|
|
return bl_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_all_br_names(self):
|
|
|
|
|
""" Creates a list of all br pins names """
|
2018-09-07 04:36:50 +02:00
|
|
|
br_pins = self.rw_br_names + self.w_br_names + self.r_br_names
|
2018-09-07 02:59:21 +02:00
|
|
|
return br_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_read_bl_names(self):
|
|
|
|
|
""" Creates a list of bl pin names associated with read ports """
|
2018-09-07 04:36:50 +02:00
|
|
|
bl_pins = self.rw_bl_names + self.r_bl_names
|
2018-09-07 02:59:21 +02:00
|
|
|
return bl_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_read_br_names(self):
|
|
|
|
|
""" Creates a list of br pin names associated with read ports """
|
2018-09-07 04:36:50 +02:00
|
|
|
br_pins = self.rw_br_names + self.r_br_names
|
2018-09-07 02:59:21 +02:00
|
|
|
return br_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_write_bl_names(self):
|
|
|
|
|
""" Creates a list of bl pin names associated with write ports """
|
2018-09-07 04:36:50 +02:00
|
|
|
bl_pins = self.rw_bl_names + self.w_bl_names
|
2018-09-07 02:59:21 +02:00
|
|
|
return bl_pins
|
2018-08-07 18:44:01 +02:00
|
|
|
|
2018-08-26 23:37:17 +02:00
|
|
|
def list_write_br_names(self):
|
|
|
|
|
""" Creates a list of br pin names asscociated with write ports"""
|
2018-09-07 04:36:50 +02:00
|
|
|
br_pins = self.rw_br_names + self.w_br_names
|
2018-09-14 01:53:24 +02:00
|
|
|
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_left.get_pin("D").uc()
|
|
|
|
|
vdd_pos = vector(Q_bar_pos.x, self.vdd_position.y)
|
|
|
|
|
|
|
|
|
|
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|