From 4d3f01ff2f9ae48ff943f594dfdf4d8659ef92e3 Mon Sep 17 00:00:00 2001 From: Michael Timothy Grimes Date: Wed, 28 Feb 2018 11:14:53 -0800 Subject: [PATCH] The bitcell currently passes DRC and LVS for FreePDK45 and SCMOS There are 2 benchtests for the bitcell: 1) one with 2 write ports and 2 read ports 2) one with 2 write ports and 0 read ports The second test is meant to show how the bitcell functions when read/write ports are used instead of separate ports for read and write The bitcell currently passes both tests in both technologies Certain sizing optimizations still need to be done on the bitcell --- compiler/pbitcell.py | 735 +++++++++++++++----------- compiler/tests/04_pbitcell_0R_test.py | 43 ++ compiler/tests/04_pbitcell_0W_test.py | 60 --- compiler/tests/04_pbitcell_test.py | 33 +- 4 files changed, 468 insertions(+), 403 deletions(-) create mode 100644 compiler/tests/04_pbitcell_0R_test.py delete mode 100644 compiler/tests/04_pbitcell_0W_test.py diff --git a/compiler/pbitcell.py b/compiler/pbitcell.py index 86bc5e9d..871b5ef0 100644 --- a/compiler/pbitcell.py +++ b/compiler/pbitcell.py @@ -1,104 +1,131 @@ +import contact +import pgate import design import debug from tech import drc, parameter, spice from vector import vector -import contact -from math import ceil from ptx import ptx from globals import OPTS -class pbitcell(design.design): +class pbitcell(pgate.pgate): """ This module implements a parametrically sized multi-port bitcell """ def __init__(self, num_write=1, num_read=1): - name = "pbitcell" - design.design.__init__(self, name) + name = "pbitcell_{0}W_{1}R".format(num_write, num_read) + pgate.pgate.__init__(self, name) debug.info(2, "create a multi-port bitcell with {0} write ports and {1} read ports".format(num_write, num_read)) self.num_write = num_write self.num_read = num_read + self.add_pins() self.create_layout() self.DRC_LVS() def add_pins(self): for k in range(0,self.num_write): - self.add_pin("WROW{}".format(k)) + self.add_pin("wrow{}".format(k)) for k in range(0,self.num_write): - self.add_pin("WBL{}".format(k)) - self.add_pin("WBL_bar{}".format(k)) + self.add_pin("wbl{}".format(k)) + self.add_pin("wbl_bar{}".format(k)) for k in range(0,self.num_read): - self.add_pin("RROW{}".format(k)) + self.add_pin("rrow{}".format(k)) for k in range(0,self.num_read): - self.add_pin("RBL{}".format(k)) - self.add_pin("RBL_bar{}".format(k)) + self.add_pin("rbl{}".format(k)) + self.add_pin("rbl_bar{}".format(k)) self.add_pin("vdd") self.add_pin("gnd") def create_layout(self): - self.create_ptx() + self.add_globals() self.add_storage() self.add_rails() - if(self.num_write > 0): - self.add_write_transistors() + self.add_write_ports() if(self.num_read > 0): - self.add_read_transistors() + self.add_read_ports() self.extend_well() #self.add_fail() - def create_ptx(self): - """ Create ptx for all transistors. Also define measurements to be used throughout bitcell """ + def add_globals(self): + """ Calculate transistor sizes """ + # if there are no read ports then write transistors are being used as read/write ports like in a 6T bitcell + if(self.num_read == 0): + inverter_nmos_width = 3*parameter["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + write_nmos_width = 1.5*parameter["min_tx_size"] + read_nmos_width = parameter["min_tx_size"] + # used for the dual port case where there are separate write and read ports + else: + inverter_nmos_width = 2*parameter["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + write_nmos_width = parameter["min_tx_size"] + read_nmos_width = 2*parameter["min_tx_size"] + + """ Create ptx for all transistors """ # create ptx for inverter transistors - self.i_nmos = ptx(width=2*parameter["min_tx_size"], - tx_type="nmos", - connect_active=True, - connect_poly=True) - self.add_mod(self.i_nmos) + self.inverter_nmos = ptx(width=inverter_nmos_width, + tx_type="nmos") + self.add_mod(self.inverter_nmos) - self.i_pmos = ptx(width=parameter["min_tx_size"], - tx_type="pmos", - connect_active=True, - connect_poly=True) - self.add_mod(self.i_pmos) + self.inverter_pmos = ptx(width=inverter_pmos_width, + tx_type="pmos") + self.add_mod(self.inverter_pmos) # create ptx for write transitors - self.w_nmos = ptx(width=parameter["min_tx_size"], - tx_type="nmos", - connect_active=True, - connect_poly=True) - self.add_mod(self.w_nmos) + self.write_nmos = ptx(width=write_nmos_width, + tx_type="nmos") + self.add_mod(self.write_nmos) # create ptx for read transistors - self.r_nmos = ptx(width=2*parameter["min_tx_size"], - mults=2, - tx_type="nmos", - connect_active=False, - connect_poly=False) - self.add_mod(self.r_nmos) + self.read_nmos = ptx(width=read_nmos_width, + tx_type="nmos") + self.add_mod(self.read_nmos) + """ Define pbitcell global variables """ # determine metal contact extensions - self.ip_ex = 0.5*(self.i_pmos.active_contact.height - self.i_pmos.active_height) - self.w_ex = 0.5*(self.w_nmos.active_contact.height - self.w_nmos.active_height) + self.ip_ex = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) + self.w_ex = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height) - # determine global measurements - w_spacer = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] - r_spacer = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width - rw_spacer = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex + # calculation for transistor spacing + self.write_to_write_spacing = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] + self.read_to_read_spacing = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width + self.write_to_read_spacing = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex - self.w_tile_width = w_spacer + self.w_nmos.active_height - self.r_tile_width = r_spacer + self.r_nmos.active_height - self.inv_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.ip_ex - self.cross_heightL = self.i_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width - self.cross_heightU = self.i_nmos.active_height + drc["poly_to_active"] + drc["poly_to_field_poly"] + 1.5*contact.poly.width + # calculations for transistor tiling (includes transistor and spacing) + self.inverter_tile_width = self.inverter_nmos.active_width + 1.5*parameter["min_tx_size"] + 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 - self.leftmost_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) \ - - self.num_write*(w_spacer + self.w_nmos.active_height) \ - - rw_spacer - self.r_nmos.active_height - (self.num_read-1)*self.r_tile_width \ - - 3*drc["minwidth_metal1"] - self.botmost_ypos = -2*drc["minwidth_metal1"] - self.num_write*2*drc["minwidth_metal2"] - self.num_read*2*drc["minwidth_metal2"] - self.topmost_ypos = self.inv_gap + self.i_nmos.active_height + self.i_pmos.active_height + drc["poly_extend_active"] + 2*drc["minwidth_metal1"] + # calculation for row line tiling + self.rowline_tile_height = drc["minwidth_metal1"] + contact.m1m2.width + + # calculations related to inverter connections + self.inverter_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.ip_ex + self.cross_couple_lower_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width + self.cross_couple_upper_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + drc["poly_to_field_poly"] + 1.5*contact.poly.width + + # calculations for the edges of the cell + if(self.num_read > 0): + read_port_flag = 1; + else: + read_port_flag = 0; + + self.leftmost_xpos = -self.inverter_tile_width \ + - self.num_write*self.write_tile_width \ + - read_port_flag*(self.write_to_read_spacing - self.read_nmos.active_height - (self.num_read-1)*self.read_tile_width) \ + - drc["minwidth_poly"] - contact.m1m2.height - 0.5*drc["minwidth_metal2"] + + self.rightmost_xpos = -self.leftmost_xpos + + self.botmost_ypos = -self.rowline_tile_height \ + - self.num_write*self.rowline_tile_height \ + - read_port_flag*(self.num_read*self.rowline_tile_height) + + self.topmost_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + drc["poly_extend_active"] + 2*drc["minwidth_metal1"] + + # calculations for the cell dimensions self.cell_width = -2*self.leftmost_xpos self.cell_height = self.topmost_ypos - self.botmost_ypos @@ -106,439 +133,511 @@ class pbitcell(design.design): def add_storage(self): """ Creates the crossed coupled inverters that act as storage for the bitcell. + The stored value of the cell is denoted as "Q" and the inverted values as "Q_bar". """ - lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) - rit_xpos = 1.5*parameter["min_tx_size"] - it_ypos = self.inv_gap + self.i_nmos.active_height + + # calculate transistor offsets + left_inverter_xpos = -(self.inverter_nmos.active_width + 1.5*parameter["min_tx_size"]) + right_inverter_xpos = 1.5*parameter["min_tx_size"] + inverter_pmos_ypos = self.inverter_gap + self.inverter_nmos.active_height - # create active - self.i_nmosL = self.add_inst(name="i_nmosL", - mod=self.i_nmos, - offset=[lit_xpos,0]) + # create active for nmos + self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", + mod=self.inverter_nmos, + offset=[left_inverter_xpos,0]) self.connect_inst(["Q_bar", "Q", "gnd", "gnd"]) - self.i_nmosR = self.add_inst(name="i_nmosR", - mod=self.i_nmos, - offset=[rit_xpos,0]) + self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", + mod=self.inverter_nmos, + offset=[right_inverter_xpos,0]) self.connect_inst(["gnd", "Q_bar", "Q", "gnd"]) - - self.i_pmosL = self.add_inst(name="i_pmosL", - mod=self.i_pmos, - offset=[lit_xpos, it_ypos]) + + # create active for pmos + self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", + mod=self.inverter_pmos, + offset=[left_inverter_xpos, inverter_pmos_ypos]) self.connect_inst(["Q_bar", "Q", "vdd", "vdd"]) - self.i_pmosR = self.add_inst(name="i_pmosR", - mod=self.i_pmos, - offset=[rit_xpos, it_ypos]) + self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", + mod=self.inverter_pmos, + offset=[right_inverter_xpos, inverter_pmos_ypos]) self.connect_inst(["vdd", "Q_bar", "Q", "vdd"]) - # connect poly for inverters - self.add_path("poly", [self.i_nmosL.get_pin("G").uc(), self.i_pmosL.get_pin("G").bc()]) - self.add_path("poly", [self.i_nmosR.get_pin("G").uc(), self.i_pmosR.get_pin("G").bc()]) + # 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 drains for inverters - self.add_path("metal1", [self.i_nmosL.get_pin("D").uc(), self.i_pmosL.get_pin("D").bc()]) - self.add_path("metal1", [self.i_nmosR.get_pin("S").uc(), self.i_pmosR.get_pin("S").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.width) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.width) # add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar) - offsetL = vector(self.i_nmosL.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_heightU) + offsetL = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.width, self.cross_couple_upper_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=offsetL, rotate=90) - offsetR = vector(self.i_nmosR.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_heightL) + offsetR = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.width, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=offsetR, rotate=90) # connect contacts to gate poly (cross couple) - gate_offsetR = vector(self.i_nmosR.get_pin("G").lc().x, offsetL.y) + gate_offsetR = vector(self.inverter_nmos_right.get_pin("G").lc().x, offsetL.y) self.add_path("poly", [offsetL, gate_offsetR]) - gate_offsetL = vector(self.i_nmosL.get_pin("G").rc().x, offsetR.y) + gate_offsetL = vector(self.inverter_nmos_left.get_pin("G").rc().x, offsetR.y) self.add_path("poly", [offsetR, gate_offsetL]) def add_rails(self): + """ + Adds gnd and vdd rails and connects them to the storage element + """ + """ Add rails for vdd and gnd """ - self.gnd_position = vector(self.leftmost_xpos, -2*drc["minwidth_metal1"]) + self.gnd_position = vector(self.leftmost_xpos, -self.rowline_tile_height) self.gnd = self.add_layout_pin(text="gnd", layer="metal1", offset=self.gnd_position, width=self.cell_width, - height=drc["minwidth_metal1"]) + height=contact.well.second_layer_width) - self.vdd_position = vector(self.leftmost_xpos, self.i_pmosL.get_pin("S").uc().y + drc["minwidth_metal1"]) + self.vdd_position = vector(self.leftmost_xpos, self.inverter_pmos_left.get_pin("S").uc().y + drc["minwidth_metal1"]) self.vdd = self.add_layout_pin(text="vdd", layer="metal1", offset=self.vdd_position, width=self.cell_width, height=drc["minwidth_metal1"]) - + """ Connect inverters to rails """ # connect nmos to gnd - gnd_posL = vector(self.i_nmosL.get_pin("S").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) - self.add_path("metal1", [self.i_nmosL.get_pin("S").bc(), gnd_posL]) + gnd_posL = vector(self.inverter_nmos_left.get_pin("S").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) + self.add_path("metal1", [self.inverter_nmos_left.get_pin("S").bc(), gnd_posL]) - gnd_posR = vector(self.i_nmosR.get_pin("D").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) - self.add_path("metal1", [self.i_nmosR.get_pin("D").bc(), gnd_posR]) + gnd_posR = vector(self.inverter_nmos_right.get_pin("D").bc().x, self.gnd_position.y + drc["minwidth_metal1"]) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("D").bc(), gnd_posR]) # connect pmos to vdd - vdd_posL = vector(self.i_nmosL.get_pin("S").uc().x, self.vdd_position.y) - self.add_path("metal1", [self.i_pmosL.get_pin("S").uc(), vdd_posL]) + vdd_posL = 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_posL]) - vdd_posR = vector(self.i_nmosR.get_pin("D").uc().x, self.vdd_position.y) - self.add_path("metal1", [self.i_pmosR.get_pin("D").uc(), vdd_posR]) + vdd_posR = 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_posR]) - def add_write_transistors(self): + def add_write_ports(self): + """ + Adds write ports to the bit cell. A single transistor acts as the write port. + A write is enabled by setting a Write-Rowline (WROW) high, subsequently turning on the transistor. + The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q). + Driving WBL high or low sets the value of the cell. + This is a differential design, so each write port has a mirrored port that connects WBL_bar to Q_bar. + """ + """ Define variables relevant to write transistors """ - lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) - rit_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width - rot_correct = self.w_nmos.active_height + # define offset correction due to rotation of the ptx cell + write_rotation_correct = self.write_nmos.active_height - self.w_nmosL = [None] * self.num_write - self.w_nmosR = [None] * self.num_write + # define write transistor variables as empty arrays based on the number of write ports + self.write_nmos_left = [None] * self.num_write + self.write_nmos_right = [None] * self.num_write self.wrow_positions = [None] * self.num_write self.wbl_positions = [None] * self.num_write - self.wbl_bar_positions = [None] * self.num_write - + self.wbl_bar_positions = [None] * self.num_write + # iterate over the number of write ports for k in range(0,self.num_write): - """ Add transistors and WROW lines """ - # calculate transistor offsets - w_spacer = drc["minwidth_metal2"] + self.w_ex + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] - wrow_ypos = self.gnd_position.y - (k+1)*2*drc["minwidth_metal2"] - lwt_xpos = lit_xpos - (k+1)*self.w_tile_width + rot_correct - rwt_xpos = rit_xpos + w_spacer + k*self.w_tile_width + rot_correct + """ Add transistors """ + # calculate write transistor offsets + left_write_transistor_xpos = -self.inverter_tile_width \ + - (k+1)*self.write_tile_width + write_rotation_correct + + right_write_transistor_xpos = self.inverter_tile_width \ + + self.write_to_write_spacing + k*self.write_tile_width + write_rotation_correct # add write transistors - self.w_nmosL[k] = self.add_inst(name="w_nmosL{}".format(k), - mod=self.w_nmos, - offset=[lwt_xpos,0], - rotate=90) - self.connect_inst(["Q", "WROW{}".format(k), "WBL{}".format(k), "gnd"]) + self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), + mod=self.write_nmos, + offset=[left_write_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q", "wrow{}".format(k), "wbl{}".format(k), "gnd"]) - self.w_nmosR[k] = self.add_inst(name="w_nmosR{}".format(k), - mod=self.w_nmos, - offset=[rwt_xpos,0], - rotate=90) - self.connect_inst(["Q_bar", "WROW{}".format(k), "WBL_bar{}".format(k), "gnd"]) - - # add WROW lines + self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), + mod=self.write_nmos, + offset=[right_write_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q_bar", "wrow{}".format(k), "wbl_bar{}".format(k), "gnd"]) + + """ Add WROW lines """ + # calculate WROW position + wrow_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height self.wrow_positions[k] = vector(self.leftmost_xpos, wrow_ypos) - self.add_layout_pin(text="WROW{}".format(k), + + # add pin for WROW + self.add_layout_pin(text="wrow{}".format(k), layer="metal1", offset=self.wrow_positions[k], width=self.cell_width, - height=drc["minwidth_metal1"]) - - + height=contact.m1m2.width) + """ Source/WBL/WBL_bar connections """ - # add contacts to connect source of wt to WBL or WBL_bar - offsetL = self.w_nmosL[k].get_pin("S").center() + # add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar + offsetL = self.write_nmos_left[k].get_pin("S").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetL, rotate=90) - offsetR = self.w_nmosR[k].get_pin("S").center() + offsetR = self.write_nmos_right[k].get_pin("S").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetR, rotate=90) - # add WBL and WBL_bar (simultaneously connects to source of wt) - self.wbl_positions[k] = vector(self.w_nmosL[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) - self.add_layout_pin(text="WBL{}".format(k), + # add pins for WBL and WBL_bar, overlaid on source contacts + self.wbl_positions[k] = vector(self.write_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="wbl{}".format(k), layer="metal2", offset=self.wbl_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) - self.wbl_bar_positions[k] = vector(self.w_nmosR[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) - self.add_layout_pin(text="WBL_bar{}".format(k), + self.wbl_bar_positions[k] = vector(self.write_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="wbl_bar{}".format(k), layer="metal2", offset=self.wbl_bar_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) - - + """ Gate/WROW connections """ - # add contacts to connect gate of wt to WROW (poly to metal2) - offsetL = vector(self.w_nmosL[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width, self.w_nmosL[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height) + # add poly-to-meltal2 contacts to connect gate of write transistors to WROW (contact next to gate) + contact_xpos = self.write_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + contact_ypos = self.write_nmos_left[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + left_gate_contact = vector(contact_xpos, contact_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetL) - + offset=left_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offsetL) - + offset=left_gate_contact) self.add_rect_center(layer="poly", - offset=offsetL, + offset=left_gate_contact, width=contact.poly.width, height=contact.poly.height) - offsetR = vector(self.w_nmosR[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width, self.w_nmosR[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height) + + contact_xpos = self.write_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + contact_ypos = self.write_nmos_right[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + right_gate_contact = vector(contact_xpos, contact_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetR) - + offset=right_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offsetR) - + offset=right_gate_contact) self.add_rect_center(layer="poly", - offset=offsetR, + offset=right_gate_contact, width=contact.poly.width, - height=contact.poly.height) + height=contact.poly.height) - # connect gate of wt to contact - midL = vector(offsetL.x, self.w_nmosL[k].get_pin("G").lc().y) - self.add_path("poly", [self.w_nmosL[k].get_pin("G").lc(), midL, offsetL]) + # 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]) - midR = vector(offsetR.x, self.w_nmosR[k].get_pin("G").rc().y) - self.add_path("poly", [self.w_nmosR[k].get_pin("G").rc(), midR, offsetR]) + 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]) - # add contacts to WROW lines - offset = vector(offsetL.x, self.wrow_positions[k].y + 0.5*drc["minwidth_metal1"]) + # add metal1-to-metal2 contacts to WROW lines + left_wrow_contact = vector(left_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offset, + offset=left_wrow_contact, rotate=90) - offset = vector(offsetR.x, self.wrow_positions[k].y + 0.5*drc["minwidth_metal1"]) + right_wrow_contact = vector(right_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offset, + offset=right_wrow_contact, rotate=90) - # connect wt gate contacts to WROW contacts - wrow_offset = vector(offsetL.x, self.wrow_positions[k].y) - self.add_path("metal2", [offsetL, wrow_offset]) - - wrow_offset = vector(offsetR.x, self.wrow_positions[k].y) - self.add_path("metal2", [offsetR, wrow_offset]) - - + # connect write transistor gate contacts to WROW contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_wrow_contact]) + self.add_path("metal2", [right_gate_contact, right_wrow_contact]) + """ Drain/Storage connections """ + # this path only needs to be drawn once on the last iteration of the loop if(k == self.num_write-1): - # add contacts to connect gate of inverters to drain of wt - offsetL = vector(self.i_nmosL.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_heightL) + # add contacts to connect gate of inverters to drain of write transistors + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetL, + offset=left_storage_contact, rotate=90) - offsetR = vector(self.i_nmosR.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_heightL) + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetR, + offset=right_storage_contact, rotate=90) - # connect gate of inverters to contacts - gate_offsetL = vector(self.i_nmosL.get_pin("G").lc().x, offsetL.y) - self.add_path("poly", [offsetL, gate_offsetL]) + # connect gate of inverters to contacts (poly path) + gate_offsetL = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [left_storage_contact, gate_offsetL]) - gate_offsetR = vector(self.i_nmosR.get_pin("G").rc().x, offsetR.y) - self.add_path("poly", [offsetR, gate_offsetR]) + gate_offsetR = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [right_storage_contact, gate_offsetR]) - # connect contacts to drains of wt - midL0 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], offsetL.y) - midL1 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.w_nmosL[k].get_pin("D").lc().y) - self.add_path("metal1", [offsetL, midL0, midL1, self.w_nmosL[k].get_pin("D").lc()]) + # connect contacts to drains of write transistors (metal1 path) + midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y) + midL1 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], self.write_nmos_left[k].get_pin("D").lc().y) + self.add_path("metal1", [left_storage_contact, midL0, midL1, self.write_nmos_left[k].get_pin("D").lc()]) - midR0 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], offsetR.y) - midR1 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.w_nmosR[k].get_pin("D").rc().y) - self.add_path("metal1", [offsetR, midR0, midR1, self.w_nmosR[k].get_pin("D").rc()]) + midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y) + midR1 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], self.write_nmos_right[k].get_pin("D").rc().y) + self.add_path("metal1", [right_storage_contact, midR0, midR1, self.write_nmos_right[k].get_pin("D").rc()]) - def add_read_transistors(self): - """ Define variables relevant to read transistors """ - lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"]) - rit_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width - lwt_xpos = lit_xpos - self.num_write*self.w_tile_width - rwt_xpos = rit_xpos + self.num_write*self.w_tile_width - wrow_ypos = self.gnd_position.y - self.num_write*2*drc["minwidth_metal2"] + def add_read_ports(self): + """ + Adds read ports to the bit cell. Two transistors function as a read port. + The two transistors in the port are denoted as the "read transistor" and the "read-access transistor". + The read transistor is connected to RROW (gate), RBL (drain), and the read-access transistor (source). + The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain). + A read is enabled by setting a Read-Rowline (RROW) high, subsequently turning on the read transistor. + The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is also high, the read-access transistor + is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read + using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q. + """ - rot_correct = self.r_nmos.active_height - r_spacer = 2*drc["minwidth_poly"] + drc["minwidth_metal1"] + 2*contact.poly.width - rw_spacer = drc["minwidth_poly"] + drc["poly_to_field_poly"] + drc["minwidth_metal2"] + 2*contact.poly.width + self.w_ex + """ Define variables relevant to read transistors """ + # define offset correction due to rotation of the ptx cell + read_rotation_correct = self.read_nmos.active_height - self.r_nmosL = [None] * self.num_read - self.r_nmosR = [None] * self.num_read + # calculate offset to overlap the drain of the read-access transistor with the source of the read transistor + overlap_offset = self.read_nmos.get_pin("D").ll() - self.read_nmos.get_pin("S").ll() + + # define read transistor variables as empty arrays based on the number of read ports + self.read_nmos_left = [None] * self.num_read + self.read_nmos_right = [None] * self.num_read + self.read_access_nmos_left = [None] * self.num_read + self.read_access_nmos_right = [None] * self.num_read self.rrow_positions = [None] * self.num_read self.rbl_positions = [None] * self.num_read self.rbl_bar_positions = [None] * self.num_read - + # iterate over the number of read ports for k in range(0,self.num_read): - """ Add transistors and RROW lines """ + """ Add transistors """ # calculate transistor offsets - lrt_xpos = lwt_xpos - rw_spacer - self.r_nmos.active_height - k*self.r_tile_width + rot_correct - rrt_xpos = rwt_xpos + rw_spacer + k*self.r_tile_width + rot_correct + left_read_transistor_xpos = -self.inverter_tile_width \ + - self.num_write*self.write_tile_width \ + - self.write_to_read_spacing - self.read_nmos.active_height - k*self.read_tile_width + read_rotation_correct + + right_read_transistor_xpos = self.inverter_tile_width \ + + self.num_write*self.write_tile_width \ + + self.write_to_read_spacing + k*self.read_tile_width + read_rotation_correct + + # add read-access transistors + self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left", + mod=self.read_nmos, + offset=[left_read_transistor_xpos,0], + rotate=90) + self.connect_inst(["RA_to_R_left{}".format(k), " Q_bar", "gnd", "gnd"]) + + self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right", + mod=self.read_nmos, + offset=[right_read_transistor_xpos,0], + rotate=90) + self.connect_inst(["RA_to_R_right{}".format(k), "Q", "gnd", "gnd"]) # add read transistors - self.r_nmosL[k] = self.add_inst(name="r_nmosL", - mod=self.r_nmos, - offset=[lrt_xpos,0], - rotate=90) - self.connect_inst(["RROW{}".format(k), "Q", "RBL{}".format(k), "gnd"]) + self.read_nmos_left[k] = self.add_inst(name="read_nmos_left", + mod=self.read_nmos, + offset=[left_read_transistor_xpos,overlap_offset.x], + rotate=90) + self.connect_inst(["rbl{}".format(k), "rrow{}".format(k), "RA_to_R_left{}".format(k), "gnd"]) - self.r_nmosR[k] = self.add_inst(name="r_nmosR", - mod=self.r_nmos, - offset=[rrt_xpos,0], - rotate=90) - self.connect_inst(["RROW{}".format(k), "Q_bar", "RBL_bar{}".format(k), "gnd"]) - - # add RROW lines - rrow_ypos = wrow_ypos - (k+1)*2*drc["minwidth_metal2"] + self.read_nmos_right[k] = self.add_inst(name="read_nmos_right", + mod=self.read_nmos, + offset=[right_read_transistor_xpos,overlap_offset.x], + rotate=90) + self.connect_inst(["rbl_bar{}".format(k), "rrow{}".format(k), "RA_to_R_right{}".format(k), "gnd"]) + + """ Add RROW lines """ + # calculate RROW position + rrow_ypos = self.gnd_position.y \ + - self.num_write*self.rowline_tile_height \ + - (k+1)*self.rowline_tile_height self.rrow_positions[k] = vector(self.leftmost_xpos, rrow_ypos) - self.add_layout_pin(text="RROW{}".format(k), + + # add pin for RROW + self.add_layout_pin(text="rrow{}".format(k), layer="metal1", offset=self.rrow_positions[k], width=self.cell_width, - height=drc["minwidth_metal1"]) + height=contact.m1m2.width) + + """ Source of read-access transistor / GND connection """ + # connect source of read-access transistor to GND (metal1 path) + offset = vector(self.read_access_nmos_left[k].get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.read_access_nmos_left[k].get_pin("S").bc(), offset]) - - """ Source of RA transistor / GND connection """ - offset = vector(self.r_nmosL[k].get_pins("S")[0].bc().x, self.gnd_position.y) - self.add_path("metal1", [self.r_nmosL[k].get_pins("S")[0].bc(), offset]) - - offset = vector(self.r_nmosR[k].get_pins("S")[0].bc().x, self.gnd_position.y) - self.add_path("metal1", [self.r_nmosR[k].get_pins("S")[0].bc(), offset]) - - - """ Source of R transistor / RBL & RBL_bar connection """ - # add contacts to connect source of rt to RBL or RBL_bar - offsetL = self.r_nmosL[k].get_pins("S")[1].center() + offset = 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(), offset]) + + """ 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 + offsetL = self.read_nmos_left[k].get_pin("D").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetL, rotate=90) - offsetR = self.r_nmosR[k].get_pins("S")[1].center() + offsetR = self.read_nmos_right[k].get_pin("D").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetR, rotate=90) - # add RBL and RBL_bar (simultaneously connects to source of rt) - self.rbl_positions[k] = vector(self.r_nmosL[k].get_pins("S")[1].center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) - self.add_layout_pin(text="RBL{}".format(k), + # add pins for RBL and RBL_bar, overlaid on drain contacts + self.rbl_positions[k] = vector(self.read_nmos_left[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rbl{}".format(k), layer="metal2", offset=self.rbl_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) - self.rbl_bar_positions[k] = vector(self.r_nmosR[k].get_pins("S")[1].center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) - self.add_layout_pin(text="RBL_bar{}".format(k), + self.rbl_bar_positions[k] = vector(self.read_nmos_right[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rbl_bar{}".format(k), layer="metal2", offset=self.rbl_bar_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) + + """ Gate of read transistor / RROW connection """ + # add poly-to-meltal2 contacts to connect gate of read transistors to RROW (contact next to gate) + contact_xpos = left_read_transistor_xpos - read_rotation_correct - drc["minwidth_poly"] - 0.5*contact.poly.width + contact_ypos = self.read_nmos_left[k].get_pin("G").lc().y + left_gate_contact = vector(contact_xpos, contact_ypos) - - """ Gate of R transistor / RROW connection """ - # add contact to connect gate of rt to RROW (poly to metal2) - offsetL = vector(lrt_xpos - rot_correct - drc["minwidth_poly"] - 0.5*contact.poly.width, self.r_nmosL[k].get_pins("G")[1].lc().y) self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetL) + offset=left_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offsetL) - - offsetR = vector(rrt_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width, self.r_nmosR[k].get_pins("G")[1].rc().y) + offset=left_gate_contact) + + contact_xpos = right_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width + contact_ypos = self.read_nmos_right[k].get_pin("G").rc().y + right_gate_contact = vector(contact_xpos, contact_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetR) + offset=right_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offsetR) + offset=right_gate_contact) - # connect gate of rt to contact - self.add_path("poly", [self.r_nmosL[k].get_pins("G")[1].lc(), offsetL]) - self.add_path("poly", [self.r_nmosR[k].get_pins("G")[1].rc(), offsetR]) + # 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 contacts to RROW lines - row_offsetL = vector(self.r_nmosL[k].get_pins("S")[1].lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width, self.rrow_positions[k].y + 0.5*drc["minwidth_metal1"]) + # add metal1-to-metal2 contacts to RROW lines + left_rrow_contact = vector(left_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=row_offsetL, + offset=left_rrow_contact, rotate=90) - row_offsetR = vector(self.r_nmosR[k].get_pins("S")[1].rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width, self.rrow_positions[k].y + 0.5*drc["minwidth_metal1"]) + right_rrow_contact = vector(right_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=row_offsetR, + offset=right_rrow_contact, rotate=90) - # connect rt gate contacts to RROW contacts - self.add_path("metal2", [offsetL, row_offsetL]) - self.add_path("metal2", [offsetR, row_offsetR]) - - - """ Gate of RA transistor / storage connection """ - # add contact to connect gate of rat to output of inverters - offsetL = vector(lrt_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width, self.r_nmosL[k].get_pins("G")[0].rc().y) - self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetL) + # connect read transistor gate contacts to RROW contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_rrow_contact]) + self.add_path("metal2", [right_gate_contact, right_rrow_contact]) - offsetR = vector(rrt_xpos - rot_correct - drc["minwidth_poly"] - 0.5*contact.poly.width, self.r_nmosR[k].get_pins("G")[0].lc().y) + """ Gate of read-access transistor / storage connection """ + # add poly-to-metal1 contacts to connect gate of read-access transistors to output of inverters (contact next to gate) + contact_xpos = left_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width + contact_ypos = self.read_access_nmos_left[k].get_pin("G").rc().y + left_gate_contact = vector(contact_xpos, contact_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=offsetR) + offset=left_gate_contact) - # connect gate of rat to contact - self.add_path("poly", [self.r_nmosL[k].get_pins("G")[0].rc(), offsetL]) - self.add_path("poly", [self.r_nmosR[k].get_pins("G")[0].lc(), offsetR]) + contact_xpos = right_read_transistor_xpos - read_rotation_correct - drc["minwidth_poly"] - 0.5*contact.poly.width + contact_ypos = self.read_access_nmos_right[k].get_pin("G").lc().y + right_gate_contact = vector(contact_xpos, contact_ypos) - # connect contact to output of inverters - midL0 = vector(offsetL.x, self.r_nmosL[k].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) - midL1 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.r_nmosL[0].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) - midL2 = vector(self.i_nmosL.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.cross_heightU) - gate_offsetL = vector(self.i_nmosL.get_pin("D").center().x, self.cross_heightU) - self.add_path("metal1", [offsetL, midL0, midL1, midL2, gate_offsetL]) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) - midR0 = vector(offsetR.x, self.r_nmosR[k].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) - midR1 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.r_nmosR[k].get_pins("S")[1].uc().y + 1.5*drc["minwidth_metal1"]) - midR2 = vector(self.i_nmosR.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.cross_heightU) - gate_offsetR = vector(self.i_nmosR.get_pin("S").center().x, self.cross_heightU) - self.add_path("metal1", [offsetR, midR0, midR1, midR2, gate_offsetR]) + # connect gate of read-access transistor to contact (poly path) + self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").rc(), left_gate_contact]) + self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").lc(), right_gate_contact]) + + # connect contact to output of inverters (metal1 path) + # metal1 path must route over the read transistors (above drain of read transistor) + midL0 = vector(left_gate_contact.x, self.read_nmos_left[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + ## continue metal1 path until inverter + midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.read_nmos_left[0].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + ## route down to be level with inverter output + midL2 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos) + ## endpoint at drain of inverter + left_inverter_offset = vector(self.inverter_nmos_left.get_pin("D").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [left_gate_contact, midL0, midL1, midL2, left_inverter_offset]) + + midR0 = vector(right_gate_contact.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR2 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.cross_couple_upper_ypos) + right_inverter_offset = vector(self.inverter_nmos_right.get_pin("S").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [right_gate_contact, midR0, midR1, midR2, right_inverter_offset]) def extend_well(self): - """ extend nwell and pwell """ - # extend pwell to encompass i_nmos + """ extend pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well """ offset = vector(self.leftmost_xpos, self.botmost_ypos) - well_height = -self.botmost_ypos + self.i_nmos.well_height - drc["well_enclosure_active"] + well_height = -self.botmost_ypos + self.inverter_nmos.cell_well_height - drc["well_enclosure_active"] self.add_rect(layer="pwell", offset=offset, width=self.cell_width, height=well_height) - # extend pwell to encompass r_nmos - r_well_width = self.num_read*self.r_tile_width - r_well_height = self.r_nmos.well_width - drc["well_enclosure_active"] - offset = vector(self.leftmost_xpos, 0) - self.add_rect(layer="pwell", - offset=offset, - width=r_well_width, - height=r_well_height) - - offset = vector(-self.leftmost_xpos - r_well_width, 0) - self.add_rect(layer="pwell", - offset=offset, - width=r_well_width, - height=r_well_height) - - # extend pwell to encompass w_nmos - lwt_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"] + self.w_tile_width - self.w_nmos.active_height - drc["well_enclosure_active"]) - rwt_xpos = 1.5*parameter["min_tx_size"] + self.i_nmos.active_width + self.w_tile_width - self.w_nmos.active_height - drc["well_enclosure_active"] + """ extend pwell over write transistors to the height of the write transistor well """ + left_write_well_xpos = -(self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"]) + right_write_well_xpos = self.inverter_tile_width + self.write_to_write_spacing - drc["well_enclosure_active"] - w_well_width = -(self.leftmost_xpos - lwt_xpos) - w_well_height = self.w_nmos.well_width - drc["well_enclosure_active"] - offset = vector(lwt_xpos - w_well_width, 0) - self.add_rect(layer="pwell", - offset=offset, - width=w_well_width, - height=w_well_height) - - offset = vector(rwt_xpos, 0) - self.add_rect(layer="pwell", - offset=offset, - width=w_well_width, - height=w_well_height) + write_well_width = -(self.leftmost_xpos - left_write_well_xpos) + write_well_height = self.write_nmos.cell_well_width - drc["well_enclosure_active"] - # extend nwell to encompass i_pmos - lit_xpos = -(self.i_nmos.active_width + 1.5*parameter["min_tx_size"] + drc["well_enclosure_active"]) - it_ypos = self.inv_gap + self.i_nmos.active_height - drc["well_enclosure_active"] - offset = [lit_xpos,it_ypos] - well_width = 2*self.i_pmos.active_width + 3*parameter["min_tx_size"] + 2*drc["well_enclosure_active"] - well_height = self.vdd_position.y - it_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"] + offset = vector(left_write_well_xpos - write_well_width, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + offset = vector(right_write_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + """ extend pwell over the read transistors to the height of the read transistor well """ + if(self.num_read > 0): + left_read_well_xpos = -(self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"]) + right_read_well_xpos = self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"] + + read_well_width = -(self.leftmost_xpos - left_read_well_xpos) + read_well_height = self.topmost_ypos + + offset = vector(self.leftmost_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + offset = vector(right_read_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + """ extend nwell to encompass inverter_pmos """ + 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"] + + well_width = 2*self.inverter_pmos.active_width + 3*parameter["min_tx_size"] + 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, @@ -547,7 +646,7 @@ class pbitcell(design.design): """ add well contacts """ # connect pimplants to gnd - offset = vector(0, self.gnd_position.y + 0.5*drc["minwidth_metal1"]) + offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90) @@ -570,7 +669,7 @@ class pbitcell(design.design): def add_fail(self): - # for failing drc + # for failing drc when I want to observe the gds layout frail_width = self.well_width = 2*drc["minwidth_metal1"] frail_height = self.rail_height = drc["minwidth_metal1"] diff --git a/compiler/tests/04_pbitcell_0R_test.py b/compiler/tests/04_pbitcell_0R_test.py new file mode 100644 index 00000000..cb274548 --- /dev/null +++ b/compiler/tests/04_pbitcell_0R_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python2.7 +""" +Run regresion tests on a parameterized bitcell +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +OPTS = globals.OPTS + +#@unittest.skip("SKIPPING 04_pbitcell_test") + + +class pbitcell_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + global verify + import verify + OPTS.check_lvsdrc = False + + import pbitcell + import tech + + debug.info(2, "Bitcell with 2 write ports and 0 read ports") + tx = pbitcell.pbitcell(num_write=2,num_read=0) + self.local_check(tx) + + OPTS.check_lvsdrc = True + globals.end_openram() + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/04_pbitcell_0W_test.py b/compiler/tests/04_pbitcell_0W_test.py deleted file mode 100644 index 38b1dd25..00000000 --- a/compiler/tests/04_pbitcell_0W_test.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python2.7 -""" -Run regresion tests on a parameterized inverter -""" - -import unittest -from testutils import header -import sys,os -sys.path.append(os.path.join(sys.path[0],"..")) -import globals -import debug -import verify - -OPTS = globals.OPTS - -#@unittest.skip("SKIPPING 04_pbitcell_test") - - -class pbitcell_test(unittest.TestCase): - - def runTest(self): - globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - OPTS.check_lvsdrc = False - - import pbitcell - import tech - - debug.info(2, "Test for pbitcell with 0 write ports and 2 read ports") - tx = pbitcell.pbitcell(num_write=0,num_read=2) - self.local_check(tx) - - OPTS.check_lvsdrc = True - globals.end_openram() - - def local_check(self, tx): - tempspice = OPTS.openram_temp + "temp.sp" - tempgds = OPTS.openram_temp + "temp.gds" - - tx.sp_write(tempspice) - tx.gds_write(tempgds) - - self.assertFalse(verify.run_drc(tx.name, tempgds)) - self.assertFalse(verify.run_lvs(tx.name, tempgds, tempspice)) - - os.remove(tempspice) - os.remove(tempgds) - - # reset the static duplicate name checker for unit tests - import design - design.design.name_map=[] - - - - -# instantiate a copy of the class to actually run the test -if __name__ == "__main__": - (OPTS, args) = globals.parse_args() - del sys.argv[1:] - header(__file__, OPTS.tech_name) - unittest.main() diff --git a/compiler/tests/04_pbitcell_test.py b/compiler/tests/04_pbitcell_test.py index 3ba42b76..36c251b5 100644 --- a/compiler/tests/04_pbitcell_test.py +++ b/compiler/tests/04_pbitcell_test.py @@ -1,55 +1,38 @@ #!/usr/bin/env python2.7 """ -Run regresion tests on a parameterized inverter +Run regresion tests on a parameterized bitcell """ import unittest -from testutils import header +from testutils import header,openram_test import sys,os sys.path.append(os.path.join(sys.path[0],"..")) import globals +from globals import OPTS import debug -import verify OPTS = globals.OPTS #@unittest.skip("SKIPPING 04_pbitcell_test") -class pbitcell_test(unittest.TestCase): +class pbitcell_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + global verify + import verify OPTS.check_lvsdrc = False import pbitcell import tech - debug.info(2, "Test for pbitcell with 2 write ports and 2 read ports") + debug.info(2, "Bitcell with 2 write ports and 2 read ports") tx = pbitcell.pbitcell(num_write=2,num_read=2) self.local_check(tx) OPTS.check_lvsdrc = True - globals.end_openram() - - def local_check(self, tx): - #tempspice = OPTS.openram_temp + "temp.sp" - tempgds = OPTS.openram_temp + "temp.gds" - - #tx.sp_write(tempspice) - tx.gds_write(tempgds) - - self.assertFalse(verify.run_drc(tx.name, tempgds)) - #self.assertFalse(verify.run_lvs(tx.name, tempgds, tempspice)) - - #os.remove(tempspice) - os.remove(tempgds) - - # reset the static duplicate name checker for unit tests - import design - design.design.name_map=[] - - + globals.end_openram() # instantiate a copy of the class to actually run the test