From 1a0568f2440050dd3abc3dabb6d361aec6afa77b Mon Sep 17 00:00:00 2001 From: Michael Timothy Grimes Date: Sun, 21 Oct 2018 19:10:04 -0700 Subject: [PATCH] Updating comments and cleaning up code for pbitcell. --- compiler/bitcells/pbitcell.py | 499 +++++++++++++++------------------- 1 file changed, 222 insertions(+), 277 deletions(-) diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 5816e350..908df0e0 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -11,52 +11,51 @@ class pbitcell(design.design): This module implements a parametrically sized multi-port bitcell, with a variable number of read/write, write, and read ports """ - + def __init__(self, replica_bitcell=False): - + self.num_rw_ports = OPTS.num_rw_ports self.num_w_ports = OPTS.num_w_ports self.num_r_ports = OPTS.num_r_ports self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports - + self.replica_bitcell = replica_bitcell - + if self.replica_bitcell: name = "replica_pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports) else: name = "pbitcell_{0}RW_{1}W_{2}R".format(self.num_rw_ports, self.num_w_ports, self.num_r_ports) - # This is not a pgate because pgates depend on the bitcell height! + design.design.__init__(self, name) debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, self.num_w_ports, - self.num_r_ports)) + self.num_r_ports)) self.create_netlist() - # We must always create the bitcell layout because - # some transistor sizes in the other netlists depend on it + # We must always create the bitcell layout because some transistor sizes in the other netlists depend on it self.create_layout() - + def create_netlist(self): self.add_pins() self.add_modules() self.create_storage() - + if(self.num_rw_ports > 0): self.create_readwrite_ports() if(self.num_w_ports > 0): self.create_write_ports() if(self.num_r_ports > 0): self.create_read_ports() - + def create_layout(self): self.calculate_spacing() self.calculate_postions() - + self.place_storage() self.route_storage() - + self.route_rails() - + if(self.num_rw_ports > 0): self.place_readwrite_ports() self.route_readwrite_access() @@ -67,23 +66,24 @@ class pbitcell(design.design): self.place_read_ports() self.route_read_access() self.extend_well() - + self.route_wordlines() self.route_bitlines() self.route_supply() - + if self.replica_bitcell: self.route_rbc_short() - - # in netlist_only mode, calling offset_all_coordinates will not be possible + + # in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible # this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though if not OPTS.netlist_only: self.offset_all_coordinates() gnd_overlap = vector(0, 0.5*contact.well.width) self.translate_all(gnd_overlap) self.DRC_LVS() - + def add_pins(self): + """ add pins and set names for bitlines and wordlines """ self.rw_bl_names = [] self.rw_br_names = [] self.w_bl_names = [] @@ -94,7 +94,7 @@ class pbitcell(design.design): self.w_wl_names = [] self.r_wl_names = [] port = 0 - + for k in range(self.num_rw_ports): self.add_pin("bl{}".format(port)) self.add_pin("br{}".format(port)) @@ -113,7 +113,7 @@ class pbitcell(design.design): self.r_bl_names.append("bl{}".format(port)) self.r_br_names.append("br{}".format(port)) port += 1 - + port = 0 for k in range(self.num_rw_ports): self.add_pin("wl{}".format(port)) @@ -127,19 +127,18 @@ class pbitcell(design.design): self.add_pin("wl{}".format(port)) self.r_wl_names.append("wl{}".format(port)) port += 1 - + self.add_pin("vdd") self.add_pin("gnd") - + + # if this is a replica bitcell, replace the instances of Q_bar with vdd if self.replica_bitcell: self.Q_bar = "vdd" else: self.Q_bar = "Q_bar" - + def add_modules(self): - """ - Determine size of transistors and add ptx modules - """ + """ Determine size of transistors and add ptx modules """ # if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports if(self.num_rw_ports > 0): inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"] @@ -147,7 +146,7 @@ class pbitcell(design.design): readwrite_nmos_width = parameter["6T_access_size"] write_nmos_width = parameter["6T_access_size"] read_nmos_width = 2*parameter["6T_inv_pmos_size"] - + # if there are no read/write ports, then the inverter nmos is statically sized for the dual port case else: inverter_nmos_width = 2*parameter["6T_inv_pmos_size"] @@ -155,7 +154,7 @@ class pbitcell(design.design): readwrite_nmos_width = parameter["6T_access_size"] write_nmos_width = parameter["6T_access_size"] read_nmos_width = 2*parameter["6T_inv_pmos_size"] - + # create ptx for inverter transistors self.inverter_nmos = ptx(width=inverter_nmos_width, tx_type="nmos") @@ -164,46 +163,46 @@ class pbitcell(design.design): 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): + + def calculate_spacing(self): """ Calculate transistor spacings """ # calculate metal contact extensions over transistor active readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height) read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height) max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension) - + # y-offset for the access transistor's gate contact self.gate_contact_yoffset = max_contact_extension + self.m2_space + 0.5*max(contact.poly.height, contact.m1m2.height) - + # y-position of access transistors self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset - + # y-position of inverter nmos self.inverter_nmos_ypos = self.port_ypos - + # spacing between ports - self.bitline_offset = -self.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width + self.bitline_offset = -self.active_width + 0.5*contact.m1m2.height + self.m2_space + self.m2_width self.port_spacing = self.bitline_offset + self.m2_space - + # spacing between cross coupled inverters self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space - + # calculations related to inverter connections inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) inverter_nmos_contact_extension = 0.5*(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height) @@ -217,125 +216,117 @@ class pbitcell(design.design): + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ + self.poly_to_polycontact \ + 1.5*contact.poly.width - + # spacing between wordlines (and gnd) self.rowline_spacing = self.m1_space + contact.m1m2.width - + self.rowline_offset = -0.5*self.m1_width + # spacing for vdd implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active + 0.5*(contact.well.width - self.m1_width) metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width - + # read port dimensions width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx() self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction - + def calculate_postions(self): - """ - Calculate positions that describe the edges and dimensions of the cell - """ + """ Calculate positions that describe the edges and dimensions of the cell """ self.botmost_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset - + self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \ - self.num_rw_ports*(self.readwrite_nmos.active_width + self.port_spacing) \ - self.num_w_ports*(self.write_nmos.active_width + self.port_spacing) \ - self.num_r_ports*(self.read_port_width + self.port_spacing) \ - self.bitline_offset - 0.5*self.m2_width - + self.width = -2*self.leftmost_xpos self.height = self.topmost_ypos - self.botmost_ypos - - self.y_center = 0.5*(self.topmost_ypos + self.botmost_ypos) + + self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos) def create_storage(self): """ Creates the crossed coupled inverters that act as storage for the bitcell. The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar". """ - # create active for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", mod=self.inverter_nmos) self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"]) - + self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", mod=self.inverter_nmos) self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"]) - + # create active for pmos self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", mod=self.inverter_pmos) self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"]) - + self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", mod=self.inverter_pmos) self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"]) - + def place_storage(self): - """ - Places the transistors for the crossed coupled inverters in the bitcell - """ - + """ Places the transistors for the crossed coupled inverters in the bitcell """ # calculate transistor offsets left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing inverter_pmos_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - + # create active for nmos self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos]) self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos]) - + # create active for pmos self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos]) self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos]) - + + # update furthest left and right transistor edges (this will propagate to further transistor offset calculations) self.left_building_edge = left_inverter_xpos self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width - + def route_storage(self): - """ - Routes inputs and outputs of inverters to cross couple them - """ + """ Routes inputs and outputs of inverters to cross couple them """ # connect input (gate) of inverters self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()]) - self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()]) - + self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()]) + # connect output (drain/source) of inverters self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width) self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_width) - + # add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar) contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, self.cross_couple_upper_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=contact_offset_left, rotate=90) - + contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=contact_offset_right, rotate=90) - + # connect contacts to gate poly (cross couple connections) gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y) self.add_path("poly", [contact_offset_left, gate_offset_right]) - + gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y) self.add_path("poly", [contact_offset_right, gate_offset_left]) - + def route_rails(self): - """ - Adds gnd and vdd rails and connects them to the inverters - """ + """ Adds gnd and vdd rails and connects them to the inverters """ # Add rails for vdd and gnd - gnd_ypos = -0.5*self.m1_width - self.total_ports*self.rowline_spacing + gnd_ypos = self.rowline_offset - self.total_ports*self.rowline_spacing self.gnd_position = vector(0, gnd_ypos) self.gnd = self.add_layout_pin_rect_center(text="gnd", layer="metal1", offset=self.gnd_position, width=self.width, height=self.m1_width) - + vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height + self.vdd_offset self.vdd_position = vector(0, vdd_ypos) self.vdd = self.add_layout_pin_rect_center(text="vdd", @@ -348,69 +339,61 @@ class pbitcell(design.design): """ Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell. A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor. - The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q). + 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. + This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar. """ - - # define write transistor variables as empty arrays based on the number of write ports - self.readwrite_nmos_left = [None] * self.num_rw_ports + # define read/write transistor variables as empty arrays based on the number of read/write ports + self.readwrite_nmos_left = [None] * self.num_rw_ports self.readwrite_nmos_right = [None] * self.num_rw_ports - + # iterate over the number of read/write ports for k in range(0,self.num_rw_ports): # add read/write transistors self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k), mod=self.readwrite_nmos) self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"]) - + self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), mod=self.readwrite_nmos) self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"]) def place_readwrite_ports(self): - """ - Places read/write ports in the bit cell. - """ - - # Define variables relevant to write transistors + """ Places read/write ports in the bit cell """ + # define read/write transistor variables as empty arrays based on the number of read/write ports self.rwwl_positions = [None] * self.num_rw_ports self.rwbl_positions = [None] * self.num_rw_ports self.rwbr_positions = [None] * self.num_rw_ports - + # iterate over the number of read/write ports for k in range(0,self.num_rw_ports): - # Add transistors - # calculate read/write transistor offsets + # calculate read/write transistor offsets left_readwrite_transistor_xpos = self.left_building_edge \ - (k+1)*self.port_spacing \ - (k+1)*self.readwrite_nmos.active_width - + right_readwrite_transistor_xpos = self.right_building_edge \ + (k+1)*self.port_spacing \ + k*self.readwrite_nmos.active_width - - # add read/write transistors + + # place read/write transistors self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos]) - + self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos]) - - # Add RWWL lines - # calculate RWWL position - rwwl_ypos = -0.5*self.m1_width - k*self.rowline_spacing - self.rwwl_positions[k] = vector(0, rwwl_ypos) - + # add pin for RWWL + rwwl_ypos = self.rowline_offset - k*self.rowline_spacing + self.rwwl_positions[k] = vector(0, rwwl_ypos) self.add_layout_pin_rect_center(text=self.rw_wl_names[k], layer="metal1", offset=self.rwwl_positions[k], width=self.width, height=self.m1_width) - - # add pins for RWBL and RWBL_bar, overlaid on source contacts + + # add pins for RWBL and RWBR rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + self.m2_width - self.rwbl_positions[k] = vector(rwbl_xpos, self.y_center) + self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_bl_names[k], layer="metal2", offset=self.rwbl_positions[k], @@ -418,89 +401,76 @@ class pbitcell(design.design): height=self.height) rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.bitline_offset - self.m2_width - self.rwbr_positions[k] = vector(rwbr_xpos, self.y_center) + self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_br_names[k], layer="metal2", offset=self.rwbr_positions[k], width=drc["minwidth_metal2"], height=self.height) - - # update furthest left and right transistor edges + + # update furthest left and right transistor edges self.left_building_edge = left_readwrite_transistor_xpos self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width - + def create_write_ports(self): """ Creates write ports in the bit cell. A differential pair of transistors can write only. A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor. - The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q). + 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. + This is a differential design, so each write port has a mirrored port that connects WBR to Q_bar. """ - - # Define variables relevant to write transistors - # 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 - self.write_nmos_left = [None] * self.num_w_ports + self.write_nmos_left = [None] * self.num_w_ports self.write_nmos_right = [None] * self.num_w_ports - + # iterate over the number of write ports for k in range(0,self.num_w_ports): # add write transistors self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), mod=self.write_nmos) self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"]) - + self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), mod=self.write_nmos) self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"]) def place_write_ports(self): - """ - Places write ports in the bit cell. - """ - # Define variables relevant to write transistors + """ Places write ports in the bit cell """ + # define write transistor variables as empty arrays based on the number of write ports self.wwl_positions = [None] * self.num_w_ports self.wbl_positions = [None] * self.num_w_ports - self.wbr_positions = [None] * self.num_w_ports + self.wbr_positions = [None] * self.num_w_ports - # define offset correction due to rotation of the ptx module - write_rotation_correct = self.write_nmos.active_height - # iterate over the number of write ports for k in range(0,self.num_w_ports): # Add transistors - # calculate write transistor offsets + # calculate write transistor offsets left_write_transistor_xpos = self.left_building_edge \ - (k+1)*self.port_spacing \ - (k+1)*self.write_nmos.active_width - + right_write_transistor_xpos = self.right_building_edge \ + (k+1)*self.port_spacing \ + k*self.write_nmos.active_width - + # add write transistors self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos]) - + self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos]) - - # Add WWL lines - # calculate WWL position - wwl_ypos = rwwl_ypos = -0.5*self.m1_width - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing - self.wwl_positions[k] = vector(0, wwl_ypos) - + # add pin for WWL + wwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - k*self.rowline_spacing + self.wwl_positions[k] = vector(0, wwl_ypos) self.add_layout_pin_rect_center(text=self.w_wl_names[k], layer="metal1", offset=self.wwl_positions[k], width=self.width, height=self.m1_width) - - # add pins for WBL and WBL_bar, overlaid on source contacts + + # add pins for WBL and WBR wbl_xpos = left_write_transistor_xpos - self.bitline_offset + self.m2_width - self.wbl_positions[k] = vector(wbl_xpos, self.y_center) + self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_bl_names[k], layer="metal2", offset=self.wbl_positions[k], @@ -508,20 +478,20 @@ class pbitcell(design.design): height=self.height) wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset - self.m2_width - self.wbr_positions[k] = vector(wbr_xpos, self.y_center) + self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_br_names[k], layer="metal2", offset=self.wbr_positions[k], width=drc["minwidth_metal2"], height=self.height) - - # update furthest left and right transistor edges + + # update furthest left and right transistor edges self.left_building_edge = left_write_transistor_xpos self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width - + def create_read_ports(self): """ - Creates read ports in the bit cell. A differential pair of ports can read only. + Creates read ports in the bit cell. A differential pair of ports can read only. Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor". The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source). The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain). @@ -530,85 +500,76 @@ class pbitcell(design.design): is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q. """ - + # define read transistor variables as empty arrays based on the number of read ports - self.read_nmos_left = [None] * self.num_r_ports + self.read_nmos_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_left = [None] * self.num_r_ports self.read_access_nmos_right = [None] * self.num_r_ports - + # iterate over the number of read ports for k in range(0,self.num_r_ports): # add read-access transistors self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k), mod=self.read_nmos) self.connect_inst(["RA_to_R_left{}".format(k), self.Q_bar, "gnd", "gnd"]) - + self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k), mod=self.read_nmos) self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"]) - + # add read transistors self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), mod=self.read_nmos) self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"]) - + self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k), mod=self.read_nmos) self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"]) - + def place_read_ports(self): - """ - Places the read ports in the bit cell. - """ - # Define variables relevant to read transistors + """ Places the read ports in the bit cell """ + # define read transistor variables as empty arrays based on the number of read ports self.rwl_positions = [None] * self.num_r_ports self.rbl_positions = [None] * self.num_r_ports self.rbr_positions = [None] * self.num_r_ports - - # 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").cx() - self.read_nmos.get_pin("S").cx() - + # iterate over the number of read ports for k in range(0,self.num_r_ports): - # Add transistors # calculate transistor offsets left_read_transistor_xpos = self.left_building_edge \ - (k+1)*self.port_spacing \ - (k+1)*self.read_port_width - + right_read_transistor_xpos = self.right_building_edge \ + (k+1)*self.port_spacing \ + k*self.read_port_width - + # add read-access transistors self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos]) - + self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos]) - + # add read transistors self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos]) - + self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos]) - - # Add RWL lines - # calculate RWL position - rwl_ypos = rwwl_ypos = -0.5*self.m1_width - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing - self.rwl_positions[k] = vector(0, rwl_ypos) - + # add pin for RWL + rwl_ypos = rwwl_ypos = self.rowline_offset - self.num_rw_ports*self.rowline_spacing - self.num_w_ports*self.rowline_spacing - k*self.rowline_spacing + self.rwl_positions[k] = vector(0, rwl_ypos) self.add_layout_pin_rect_center(text=self.r_wl_names[k], layer="metal1", offset=self.rwl_positions[k], width=self.width, height=self.m1_width) - - # add pins for RBL and RBL_bar, overlaid on drain contacts + + # add pins for RBL and RBR rbl_xpos = left_read_transistor_xpos - self.bitline_offset + self.m2_width - self.rbl_positions[k] = vector(rbl_xpos, self.y_center) + self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_bl_names[k], layer="metal2", offset=self.rbl_positions[k], @@ -616,17 +577,15 @@ class pbitcell(design.design): height=self.height) rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset - self.m2_width - self.rbr_positions[k] = vector(rbr_xpos, self.y_center) + self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_br_names[k], layer="metal2", offset=self.rbr_positions[k], width=drc["minwidth_metal2"], height=self.height) - + def route_wordlines(self): - """ - Routes gate of transistors to their respective wordlines - """ + """ Routes gate of transistors to their respective wordlines """ port_transistors = [] for k in range(self.num_rw_ports): port_transistors.append(self.readwrite_nmos_left[k]) @@ -637,7 +596,7 @@ class pbitcell(design.design): for k in range(self.num_r_ports): port_transistors.append(self.read_nmos_left[k]) port_transistors.append(self.read_nmos_right[k]) - + wl_positions = [] for k in range(self.num_rw_ports): wl_positions.append(self.rwwl_positions[k]) @@ -648,37 +607,35 @@ class pbitcell(design.design): for k in range(self.num_r_ports): wl_positions.append(self.rwl_positions[k]) wl_positions.append(self.rwl_positions[k]) - - + for k in range(2*self.total_ports): gate_offset = port_transistors[k].get_pin("G").bc() port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active) wl_contact_offset = vector(gate_offset.x, wl_positions[k].y) - + + # first transistor on either side of the cross coupled inverters does not need to route to wordline on metal2 if (k == 0) or (k == 1): self.add_contact_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) - - self.add_path("poly", [gate_offset, port_contact_offset]) + + self.add_path("poly", [gate_offset, port_contact_offset]) self.add_path("metal1", [port_contact_offset, wl_contact_offset]) - + else: self.add_contact_center(layers=("poly", "contact", "metal1"), - offset=port_contact_offset) + offset=port_contact_offset) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=port_contact_offset) - + self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=wl_contact_offset, rotate=90) - - self.add_path("poly", [gate_offset, port_contact_offset]) + + self.add_path("poly", [gate_offset, port_contact_offset]) self.add_path("metal2", [port_contact_offset, wl_contact_offset]) - + def route_bitlines(self): - """ - Routes read/write transistors to their respective bitlines - """ + """ Routes read/write transistors to their respective bitlines """ left_port_transistors = [] right_port_transistors = [] for k in range(self.num_rw_ports): @@ -690,7 +647,7 @@ class pbitcell(design.design): for k in range(self.num_r_ports): left_port_transistors.append(self.read_nmos_left[k]) right_port_transistors.append(self.read_nmos_right[k]) - + bl_positions = [] br_positions = [] for k in range(self.num_rw_ports): @@ -702,164 +659,155 @@ class pbitcell(design.design): for k in range(self.num_r_ports): bl_positions.append(self.rbl_positions[k]) br_positions.append(self.rbr_positions[k]) - + for k in range(self.total_ports): port_contact_offest = left_port_transistors[k].get_pin("S").center() bl_offset = vector(bl_positions[k].x, port_contact_offest.y) - + self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=port_contact_offest) - + self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height) - + for k in range(self.total_ports): port_contact_offest = right_port_transistors[k].get_pin("D").center() br_offset = vector(br_positions[k].x, port_contact_offest.y) - + self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=port_contact_offest) - + self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height) - + def route_supply(self): - # route inverter nmos and read-access transistors to gnd + """ Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """ + # route inverter nmos and read-access nmos to gnd nmos_contact_positions = [] nmos_contact_positions.append(self.inverter_nmos_left.get_pin("S").center()) nmos_contact_positions.append(self.inverter_nmos_right.get_pin("D").center()) for k in range(self.num_r_ports): nmos_contact_positions.append(self.read_access_nmos_left[k].get_pin("D").center()) nmos_contact_positions.append(self.read_access_nmos_right[k].get_pin("S").center()) - + for position in nmos_contact_positions: self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=position) - + supply_offset = vector(position.x, self.gnd_position.y) self.add_contact_center(layers=("metal1", "via1", "metal2"), offset=supply_offset, rotate=90) - + self.add_path("metal2", [position, supply_offset]) - + # route inverter pmos to vdd vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y) self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) - + vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y) self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) - + def route_readwrite_access(self): - """ - Routes read/write transistors to the storage component of the bitcell - """ + """ Routes read/write transistors to the storage component of the bitcell """ for k in range(self.num_rw_ports): mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) self.add_path("metal1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, Q_pos]) - + mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) self.add_path("metal1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, Q_bar_pos]) - + def route_write_access(self): - """ - Routes read/write transistors to the storage component of the bitcell - """ + """ Routes read/write transistors to the storage component of the bitcell """ for k in range(self.num_w_ports): mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos) Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) self.add_path("metal1", [self.write_nmos_left[k].get_pin("D").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, Q_pos]) - + mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) self.add_path("metal1", [self.write_nmos_right[k].get_pin("S").uc(), mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, Q_bar_pos]) - + def route_read_access(self): - """ - Routes read access transistors to the storage component of the bitcell - """ + """ Routes read access transistors to the storage component of the bitcell """ + # add poly to metal1 contacts for gates of the inverters left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_upper_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=left_storage_contact, rotate=90) - + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_upper_ypos) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=right_storage_contact, rotate=90) - + inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos) self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) - + inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_upper_ypos) self.add_path("poly", [right_storage_contact, inverter_gate_offset_right]) - + + # add poly to metal1 contacts for gates of read-access transistors + # route from read-access contacts to inverter contacts on metal1 for k in range(self.num_r_ports): port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) - + self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset]) - + mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, left_storage_contact]) - + port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active) self.add_contact_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) - + self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset]) - + mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) self.add_path("metal1", [port_contact_offset, mid+vector(0,0.5*self.m1_width)], width=contact.poly.second_layer_width) self.add_path("metal1", [mid, right_storage_contact]) - + def extend_well(self): """ - Connects wells between ptx modules 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. - """ - + Connects wells between ptx modules and places well contacts""" + # extend pwell to encompass entire nmos region of the cell up to the height of the tallest nmos transistor max_nmos_well_height = max(self.inverter_nmos.cell_well_height, - self.readwrite_nmos.cell_well_height, - self.write_nmos.cell_well_height, - self.read_nmos.cell_well_height) - + self.readwrite_nmos.cell_well_height, + self.write_nmos.cell_well_height, + self.read_nmos.cell_well_height) well_height = max_nmos_well_height + self.port_ypos - self.well_enclose_active - self.gnd_position.y - - # 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) self.add_rect(layer="pwell", offset=offset, width=self.width, - height=well_height) - + height=well_height) + # extend nwell to encompass inverter_pmos # calculate offset of the left pmos well inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) - drc["well_enclosure_active"] inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"] - + # calculate width of the two combined nwells # calculate height to encompass nimplant connected to vdd well_width = 2*(self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing) + 2*drc["well_enclosure_active"] well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"] - + offset = [inverter_well_xpos,inverter_well_ypos] self.add_rect(layer="nwell", offset=offset, width=well_width, height=well_height) - - - # add well contacts + + # add well contacts # connect pimplants to gnd offset = vector(0, self.gnd_position.y) self.add_contact_center(layers=("active", "contact", "metal1"), @@ -867,15 +815,15 @@ class pbitcell(design.design): rotate=90, implant_type="p", well_type="p") - + # connect nimplants to vdd offset = vector(0, self.vdd_position.y) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90, implant_type="n", - well_type="n") - + well_type="n") + def list_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = [] @@ -887,12 +835,12 @@ class pbitcell(design.design): bitcell_pins.append("vdd") bitcell_pins.append("gnd") return bitcell_pins - + def list_all_wl_names(self): """ Creates a list of all wordline pin names """ wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names return wordline_names - + def list_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ bitline_pins = [] @@ -900,42 +848,39 @@ class pbitcell(design.design): bitline_pins.append("bl{0}".format(port)) bitline_pins.append("br{0}".format(port)) return bitline_pins - + def list_all_bl_names(self): """ Creates a list of all bl pins names """ - bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names + bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names return bl_pins - + def list_all_br_names(self): """ Creates a list of all br pins names """ - br_pins = self.rw_br_names + self.w_br_names + self.r_br_names + br_pins = self.rw_br_names + self.w_br_names + self.r_br_names return br_pins - + def list_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ - bl_pins = self.rw_bl_names + self.r_bl_names + bl_pins = self.rw_bl_names + self.r_bl_names return bl_pins - + def list_read_br_names(self): """ Creates a list of br pin names associated with read ports """ - br_pins = self.rw_br_names + self.r_br_names + br_pins = self.rw_br_names + self.r_br_names return br_pins - + def list_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ - bl_pins = self.rw_bl_names + self.w_bl_names + bl_pins = self.rw_bl_names + self.w_bl_names return bl_pins - + def list_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" - br_pins = self.rw_br_names + self.w_br_names + br_pins = self.rw_br_names + self.w_br_names return br_pins - + def route_rbc_short(self): """ route the short from Q_bar to gnd necessary for the replica bitcell """ Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() - #vdd_pos = vector(Q_bar_pos.x, self.vdd_position.y) - self.add_path("metal1", [Q_bar_pos, vdd_pos]) - \ No newline at end of file