import contact import pgate import design import debug from tech import drc, parameter, spice from vector import vector from ptx import ptx from globals import OPTS class pbitcell(pgate.pgate): """ This module implements a parametrically sized multi-port bitcell """ def __init__(self, num_write=1, num_read=1): name = "pbitcell_{0}W_{1}R".format(num_write, num_read) pgate.pgate.__init__(self, name) debug.info(2, "create a multi-port bitcell with {0} write ports and {1} read ports".format(num_write, num_read)) self.num_write = num_write self.num_read = num_read self.add_pins() self.create_layout() self.DRC_LVS() def add_pins(self): for k in range(0,self.num_write): self.add_pin("wrow{}".format(k)) for k in range(0,self.num_write): self.add_pin("wbl{}".format(k)) self.add_pin("wbl_bar{}".format(k)) for k in range(0,self.num_read): self.add_pin("rrow{}".format(k)) for k in range(0,self.num_read): self.add_pin("rbl{}".format(k)) self.add_pin("rbl_bar{}".format(k)) self.add_pin("vdd") self.add_pin("gnd") def create_layout(self): self.add_globals() self.add_storage() self.add_rails() self.add_write_ports() if(self.num_read > 0): self.add_read_ports() self.extend_well() #self.add_fail() 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.inverter_nmos = ptx(width=inverter_nmos_width, tx_type="nmos") self.add_mod(self.inverter_nmos) self.inverter_pmos = ptx(width=inverter_pmos_width, tx_type="pmos") self.add_mod(self.inverter_pmos) # create ptx for write transitors self.write_nmos = ptx(width=write_nmos_width, tx_type="nmos") self.add_mod(self.write_nmos) # create ptx for read transistors self.read_nmos = ptx(width=read_nmos_width, tx_type="nmos") self.add_mod(self.read_nmos) """ Define pbitcell global variables """ # determine metal contact extensions 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) # 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 # 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 # 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 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". """ # 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 for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", mod=self.inverter_nmos, offset=[left_inverter_xpos,0]) self.connect_inst(["Q_bar", "Q", "gnd", "gnd"]) self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", mod=self.inverter_nmos, offset=[right_inverter_xpos,0]) self.connect_inst(["gnd", "Q_bar", "Q", "gnd"]) # create active for pmos self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", mod=self.inverter_pmos, offset=[left_inverter_xpos, inverter_pmos_ypos]) self.connect_inst(["Q_bar", "Q", "vdd", "vdd"]) self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", mod=self.inverter_pmos, offset=[right_inverter_xpos, inverter_pmos_ypos]) self.connect_inst(["vdd", "Q_bar", "Q", "vdd"]) # connect input (gate) of inverters self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()]) self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()]) # connect output (drain/source) of inverters self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.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.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.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.inverter_nmos_right.get_pin("G").lc().x, offsetL.y) self.add_path("poly", [offsetL, gate_offsetR]) 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, -self.rowline_tile_height) self.gnd = self.add_layout_pin(text="gnd", layer="metal1", offset=self.gnd_position, width=self.cell_width, height=contact.well.second_layer_width) self.vdd_position = vector(self.leftmost_xpos, self.inverter_pmos_left.get_pin("S").uc().y + drc["minwidth_metal1"]) self.vdd = self.add_layout_pin(text="vdd", layer="metal1", offset=self.vdd_position, width=self.cell_width, height=drc["minwidth_metal1"]) """ Connect inverters to rails """ # connect nmos to gnd 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.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.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.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_ports(self): """ Adds write ports to the bit cell. A single transistor acts as the write port. A write is enabled by setting a Write-Rowline (WROW) high, subsequently turning on the transistor. The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q). Driving WBL high or low sets the value of the cell. This is a differential design, so each write port has a mirrored port that connects WBL_bar to Q_bar. """ """ Define variables relevant to write transistors """ # define offset correction due to rotation of the ptx cell write_rotation_correct = self.write_nmos.active_height # define write transistor variables as empty arrays based on the number of write ports self.write_nmos_left = [None] * self.num_write self.write_nmos_right = [None] * self.num_write self.wrow_positions = [None] * self.num_write self.wbl_positions = [None] * self.num_write self.wbl_bar_positions = [None] * self.num_write # iterate over the number of write ports for k in range(0,self.num_write): """ Add transistors """ # calculate write transistor offsets left_write_transistor_xpos = -self.inverter_tile_width \ - (k+1)*self.write_tile_width + write_rotation_correct right_write_transistor_xpos = self.inverter_tile_width \ + self.write_to_write_spacing + k*self.write_tile_width + write_rotation_correct # add write transistors self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), mod=self.write_nmos, offset=[left_write_transistor_xpos,0], rotate=90) self.connect_inst(["Q", "wrow{}".format(k), "wbl{}".format(k), "gnd"]) self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), mod=self.write_nmos, offset=[right_write_transistor_xpos,0], rotate=90) self.connect_inst(["Q_bar", "wrow{}".format(k), "wbl_bar{}".format(k), "gnd"]) """ Add WROW lines """ # calculate WROW position wrow_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height self.wrow_positions[k] = vector(self.leftmost_xpos, wrow_ypos) # add pin for WROW self.add_layout_pin(text="wrow{}".format(k), layer="metal1", offset=self.wrow_positions[k], width=self.cell_width, height=contact.m1m2.width) """ Source/WBL/WBL_bar connections """ # add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar offsetL = self.write_nmos_left[k].get_pin("S").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetL, rotate=90) offsetR = self.write_nmos_right[k].get_pin("S").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetR, rotate=90) # add pins for WBL and WBL_bar, overlaid on source contacts self.wbl_positions[k] = vector(self.write_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) self.add_layout_pin(text="wbl{}".format(k), layer="metal2", offset=self.wbl_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) self.wbl_bar_positions[k] = vector(self.write_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) self.add_layout_pin(text="wbl_bar{}".format(k), layer="metal2", offset=self.wbl_bar_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) """ Gate/WROW connections """ # 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=left_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=left_gate_contact) self.add_rect_center(layer="poly", offset=left_gate_contact, width=contact.poly.width, height=contact.poly.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=right_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=right_gate_contact) self.add_rect_center(layer="poly", offset=right_gate_contact, width=contact.poly.width, height=contact.poly.height) # 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(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 metal1-to-metal2 contacts to WROW lines left_wrow_contact = vector(left_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=left_wrow_contact, rotate=90) right_wrow_contact = vector(right_gate_contact.x, self.wrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=right_wrow_contact, rotate=90) # connect write transistor gate contacts to WROW contacts (metal2 path) self.add_path("metal2", [left_gate_contact, left_wrow_contact]) self.add_path("metal2", [right_gate_contact, right_wrow_contact]) """ Drain/Storage connections """ # this path only needs to be drawn once on the last iteration of the loop if(k == self.num_write-1): # add contacts to connect gate of inverters to drain of write transistors left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=left_storage_contact, rotate=90) right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=right_storage_contact, rotate=90) # connect gate of inverters to contacts (poly path) 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.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 write transistors (metal1 path) midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y) midL1 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], self.write_nmos_left[k].get_pin("D").lc().y) self.add_path("metal1", [left_storage_contact, midL0, midL1, self.write_nmos_left[k].get_pin("D").lc()]) midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y) midR1 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], self.write_nmos_right[k].get_pin("D").rc().y) self.add_path("metal1", [right_storage_contact, midR0, midR1, self.write_nmos_right[k].get_pin("D").rc()]) def add_read_ports(self): """ Adds read ports to the bit cell. Two transistors function as a read port. The two transistors in the port are denoted as the "read transistor" and the "read-access transistor". The read transistor is connected to RROW (gate), RBL (drain), and the read-access transistor (source). The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain). A read is enabled by setting a Read-Rowline (RROW) high, subsequently turning on the read transistor. The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is also high, the read-access transistor is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q. """ """ Define variables relevant to read transistors """ # define offset correction due to rotation of the ptx cell read_rotation_correct = self.read_nmos.active_height # calculate offset to overlap the drain of the read-access transistor with the source of the read transistor overlap_offset = self.read_nmos.get_pin("D").ll() - self.read_nmos.get_pin("S").ll() # define read transistor variables as empty arrays based on the number of read ports self.read_nmos_left = [None] * self.num_read self.read_nmos_right = [None] * self.num_read self.read_access_nmos_left = [None] * self.num_read self.read_access_nmos_right = [None] * self.num_read self.rrow_positions = [None] * self.num_read self.rbl_positions = [None] * self.num_read self.rbl_bar_positions = [None] * self.num_read # iterate over the number of read ports for k in range(0,self.num_read): """ Add transistors """ # calculate transistor offsets left_read_transistor_xpos = -self.inverter_tile_width \ - self.num_write*self.write_tile_width \ - self.write_to_read_spacing - self.read_nmos.active_height - k*self.read_tile_width + read_rotation_correct right_read_transistor_xpos = self.inverter_tile_width \ + self.num_write*self.write_tile_width \ + self.write_to_read_spacing + k*self.read_tile_width + read_rotation_correct # add read-access transistors self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left", mod=self.read_nmos, offset=[left_read_transistor_xpos,0], rotate=90) self.connect_inst(["RA_to_R_left{}".format(k), " Q_bar", "gnd", "gnd"]) self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right", mod=self.read_nmos, offset=[right_read_transistor_xpos,0], rotate=90) self.connect_inst(["RA_to_R_right{}".format(k), "Q", "gnd", "gnd"]) # add read transistors self.read_nmos_left[k] = self.add_inst(name="read_nmos_left", mod=self.read_nmos, offset=[left_read_transistor_xpos,overlap_offset.x], rotate=90) self.connect_inst(["rbl{}".format(k), "rrow{}".format(k), "RA_to_R_left{}".format(k), "gnd"]) self.read_nmos_right[k] = self.add_inst(name="read_nmos_right", mod=self.read_nmos, offset=[right_read_transistor_xpos,overlap_offset.x], rotate=90) self.connect_inst(["rbl_bar{}".format(k), "rrow{}".format(k), "RA_to_R_right{}".format(k), "gnd"]) """ Add RROW lines """ # calculate RROW position rrow_ypos = self.gnd_position.y \ - self.num_write*self.rowline_tile_height \ - (k+1)*self.rowline_tile_height self.rrow_positions[k] = vector(self.leftmost_xpos, rrow_ypos) # add pin for RROW self.add_layout_pin(text="rrow{}".format(k), layer="metal1", offset=self.rrow_positions[k], width=self.cell_width, 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]) 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.read_nmos_right[k].get_pin("D").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=offsetR, rotate=90) # add pins for RBL and RBL_bar, overlaid on drain contacts self.rbl_positions[k] = vector(self.read_nmos_left[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) self.add_layout_pin(text="rbl{}".format(k), layer="metal2", offset=self.rbl_positions[k], width=drc["minwidth_metal2"], height=self.cell_height) self.rbl_bar_positions[k] = vector(self.read_nmos_right[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) self.add_layout_pin(text="rbl_bar{}".format(k), layer="metal2", offset=self.rbl_bar_positions[k], width=drc["minwidth_metal2"], height=self.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) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=left_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=left_gate_contact) contact_xpos = right_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width contact_ypos = self.read_nmos_right[k].get_pin("G").rc().y right_gate_contact = vector(contact_xpos, contact_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=right_gate_contact) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=right_gate_contact) # connect gate of read transistor to contact (poly path) self.add_path("poly", [self.read_nmos_left[k].get_pin("G").lc(), left_gate_contact]) self.add_path("poly", [self.read_nmos_right[k].get_pin("G").rc(), right_gate_contact]) # add metal1-to-metal2 contacts to RROW lines left_rrow_contact = vector(left_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=left_rrow_contact, rotate=90) right_rrow_contact = vector(right_gate_contact.x, self.rrow_positions[k].y + 0.5*contact.m1m2.width) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=right_rrow_contact, rotate=90) # connect read transistor gate contacts to RROW contacts (metal2 path) self.add_path("metal2", [left_gate_contact, left_rrow_contact]) self.add_path("metal2", [right_gate_contact, right_rrow_contact]) """ Gate of read-access transistor / storage connection """ # add poly-to-metal1 contacts to connect gate of read-access transistors to output of inverters (contact next to gate) contact_xpos = left_read_transistor_xpos + drc["minwidth_poly"] + 0.5*contact.poly.width contact_ypos = self.read_access_nmos_left[k].get_pin("G").rc().y left_gate_contact = vector(contact_xpos, contact_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=left_gate_contact) contact_xpos = right_read_transistor_xpos - 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) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=right_gate_contact) # connect gate of read-access transistor to contact (poly path) self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").rc(), left_gate_contact]) self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").lc(), right_gate_contact]) # connect contact to output of inverters (metal1 path) # 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 pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well """ offset = vector(self.leftmost_xpos, self.botmost_ypos) well_height = -self.botmost_ypos + self.inverter_nmos.cell_well_height - drc["well_enclosure_active"] self.add_rect(layer="pwell", offset=offset, width=self.cell_width, height=well_height) """ 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"] write_well_width = -(self.leftmost_xpos - left_write_well_xpos) write_well_height = self.write_nmos.cell_well_width - drc["well_enclosure_active"] offset = vector(left_write_well_xpos - write_well_width, 0) self.add_rect(layer="pwell", offset=offset, width=write_well_width, height=write_well_height) offset = vector(right_write_well_xpos, 0) self.add_rect(layer="pwell", offset=offset, width=write_well_width, height=write_well_height) """ extend pwell over the read transistors to the height of the read transistor well """ if(self.num_read > 0): left_read_well_xpos = -(self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"]) right_read_well_xpos = self.inverter_tile_width + self.num_write*self.write_tile_width + self.write_to_read_spacing - drc["well_enclosure_active"] read_well_width = -(self.leftmost_xpos - left_read_well_xpos) read_well_height = self.topmost_ypos offset = vector(self.leftmost_xpos, 0) 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, height=well_height) """ add well contacts """ # connect pimplants to gnd offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90) self.add_rect_center(layer="pimplant", offset=offset, width=drc["minwidth_tx"], height=drc["minwidth_tx"]) # connect nimplants to vdd offset = vector(0, self.vdd_position.y + 0.5*drc["minwidth_metal1"]) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90) self.add_rect_center(layer="nimplant", offset=offset, width=drc["minwidth_tx"], height=drc["minwidth_tx"]) def add_fail(self): # for failing drc when I want to observe the gds layout frail_width = self.well_width = 2*drc["minwidth_metal1"] frail_height = self.rail_height = drc["minwidth_metal1"] fail_position = vector(-25*drc["minwidth_tx"], - 1.5 * drc["minwidth_metal1"] - 0.5 * frail_height) # for tiling purposes self.add_layout_pin(text="gnd", layer="metal1", offset=fail_position, width=frail_width, height=frail_height) fail_position2 = vector(-25*drc["minwidth_tx"], - 0.5 * drc["minwidth_metal1"]) self.add_layout_pin(text="gnd2", layer="metal1", offset=fail_position2, width=frail_width, height=frail_height)