From e0b9989d852df4e71443915d430a43693e679254 Mon Sep 17 00:00:00 2001 From: Michael Timothy Grimes Date: Thu, 13 Sep 2018 01:42:06 -0700 Subject: [PATCH] Adding replica_pbitcell and test for multi-ported purposes. Altering replica bitline and test to accomodate. --- compiler/modules/replica_bitline.py | 31 +- compiler/pgates/pbitcell.py | 15 +- compiler/pgates/replica_pbitcell.py | 1177 ++++++++++++++++++++ compiler/tests/04_replica_pbitcell_test.py | 46 + compiler/tests/14_replica_bitline_test.py | 21 + 5 files changed, 1272 insertions(+), 18 deletions(-) create mode 100644 compiler/pgates/replica_pbitcell.py create mode 100644 compiler/tests/04_replica_pbitcell_test.py mode change 100755 => 100644 compiler/tests/14_replica_bitline_test.py diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py index 53430ac6..f4347936 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -144,6 +144,7 @@ class replica_bitline(design.design): self.connect_inst(temp) self.wl_list = self.rbl.cell.list_all_wl_names() + self.bl_list = self.rbl.cell.list_write_bl_names() def place_modules(self): """ Add all of the module instances in the logical netlist """ @@ -178,14 +179,20 @@ class replica_bitline(design.design): wl = self.wl_list[0]+"[{}]".format(row) pin = self.rbl_inst.get_pin(wl) - # Route the connection to the right so that it doesn't interfere - # with the cells + # Route the connection to the right so that it doesn't interfere with the cells + # Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions + if row % 2 == 0: + vertical_extension = vector(0, 1.5*drc["minwidth_metal1"] + 0.5*contact.m1m2.height) + else: + vertical_extension = vector(0, -1.5*drc["minwidth_metal1"] - 1.5*contact.m1m2.height) + pin_right = pin.rc() - pin_extension = pin_right + vector(self.m1_pitch,0) + pin_extension1 = pin_right + vector(self.m3_pitch,0) + pin_extension2 = pin_extension1 + vertical_extension if pin.layer != "metal1": continue - self.add_path("metal1", [pin_right, pin_extension]) - self.add_power_pin("gnd", pin_extension) + self.add_path("metal1", [pin_right, pin_extension1, pin_extension2]) + self.add_power_pin("gnd", pin_extension2) def route_supplies(self): @@ -243,11 +250,13 @@ class replica_bitline(design.design): # 3. Route the contact of previous route to the bitcell WL # route bend of previous net to bitcell WL - wl_offset = self.rbc_inst.get_pin("wl").lc() - xmid_point= 0.5*(wl_offset.x+contact_offset.x) - wl_mid1 = vector(xmid_point,contact_offset.y) - wl_mid2 = vector(xmid_point,wl_offset.y) - self.add_path("metal1", [contact_offset, wl_mid1, wl_mid2, wl_offset]) + wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc() + wl_mid1 = wl_offset - vector(1.5*drc["minwidth_metal1"], 0) + wl_mid2 = vector(wl_mid1.x, contact_offset.y) + #xmid_point= 0.5*(wl_offset.x+contact_offset.x) + #wl_mid1 = vector(xmid_point,contact_offset.y) + #wl_mid2 = vector(xmid_point,wl_offset.y) + self.add_path("metal1", [wl_offset, wl_mid1, wl_mid2, contact_offset]) # DRAIN ROUTE # Route the drain to the vdd rail @@ -262,7 +271,7 @@ class replica_bitline(design.design): # Route the connection of the source route to the RBL bitline (left) # Via will go halfway down from the bitcell - bl_offset = self.rbc_inst.get_pin("bl").bc() + bl_offset = self.rbc_inst.get_pin(self.bl_list[0]).bc() # Route down a pitch so we can use M2 routing bl_down_offset = bl_offset - vector(0, self.m2_pitch) self.add_path("metal2",[source_offset, bl_down_offset, bl_offset]) diff --git a/compiler/pgates/pbitcell.py b/compiler/pgates/pbitcell.py index 1eac5d3c..17720879 100644 --- a/compiler/pgates/pbitcell.py +++ b/compiler/pgates/pbitcell.py @@ -18,7 +18,7 @@ class pbitcell(design.design): 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 - + 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) @@ -30,7 +30,7 @@ class pbitcell(design.design): # 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() @@ -346,6 +346,7 @@ class pbitcell(design.design): self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos]) self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos]) + def route_storage(self): """ Routes inputs and outputs of inverters to cross couple them @@ -394,13 +395,13 @@ class pbitcell(design.design): height=contact.well.second_layer_width) vdd_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \ - + drc["active_to_body_active"] + 0.5*(drc["minwidth_tx"] - drc["minwidth_metal1"]) + + drc["active_to_body_active"] self.vdd_position = vector(self.leftmost_xpos, vdd_ypos) self.vdd = self.add_layout_pin(text="vdd", layer="metal1", offset=self.vdd_position, width=self.width, - height=drc["minwidth_metal1"]) + height=contact.well.second_layer_width) # Connect inverters to rails # connect inverter nmos to gnd @@ -848,7 +849,7 @@ class pbitcell(design.design): # 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), " Q_bar", "gnd", "gnd"]) + 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{}".format(k), mod=self.read_nmos) @@ -1156,7 +1157,7 @@ class pbitcell(design.design): well_type="p") # connect nimplants to vdd - offset = vector(0, self.vdd_position.y + 0.5*drc["minwidth_metal1"]) + offset = vector(0, self.vdd_position.y + 0.5*contact.well.second_layer_width) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90, @@ -1217,4 +1218,4 @@ class pbitcell(design.design): def list_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" br_pins = self.rw_br_names + self.w_br_names - return br_pins + return br_pins \ No newline at end of file diff --git a/compiler/pgates/replica_pbitcell.py b/compiler/pgates/replica_pbitcell.py new file mode 100644 index 00000000..b3845038 --- /dev/null +++ b/compiler/pgates/replica_pbitcell.py @@ -0,0 +1,1177 @@ +import contact +import design +import debug +from tech import drc, parameter, spice +from vector import vector +from ptx import ptx +from globals import OPTS + +class replica_pbitcell(design.design): + """ + Creates a replica bitcell using pbitcell + """ + + def __init__(self): + + 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 + + name = "replica_pbitcell" + # This is not a pgate because pgates depend on the bitcell height! + design.design.__init__(self, name) + debug.info(2, "create a replica pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, + self.num_w_ports, + self.num_r_ports)) + + self.create_netlist() + # We must always create the bitcell layout because + # some transistor sizes in the other netlists depend on it + self.create_layout() + + + def create_netlist(self): + self.add_pins() + self.add_modules() + self.create_storage() + + if(self.num_rw_ports > 0): + self.create_readwrite_ports() + if(self.num_w_ports > 0): + self.create_write_ports() + if(self.num_r_ports > 0): + self.create_read_ports() + + def create_layout(self): + self.calculate_spacing() + self.calculate_postions() + + self.place_storage() + self.route_storage() + self.route_rails() + + if(self.num_rw_ports > 0): + self.place_readwrite_ports() + self.route_readwrite_wordlines() + self.route_readwrite_bitlines() + if(self.num_w_ports == 0): # routing for write to storage is the same as read/write to storage + self.route_readwrite_access() + if(self.num_w_ports > 0): + self.place_write_ports() + self.route_write_wordlines() + self.route_write_bitlines() + self.route_write_access() + if(self.num_r_ports > 0): + self.place_read_ports() + self.route_read_wordlines() + self.route_read_bitlines() + self.route_read_access() + self.extend_well() + self.route_rbc_short() + + # in netlist_only mode, calling offset_all_coordinates will not be possible + # this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though + if not OPTS.netlist_only: + self.offset_all_coordinates() + self.DRC_LVS() + + def add_pins(self): + self.rw_bl_names = [] + self.rw_br_names = [] + self.w_bl_names = [] + self.w_br_names = [] + self.r_bl_names = [] + self.r_br_names = [] + self.rw_wl_names = [] + self.w_wl_names = [] + self.r_wl_names = [] + port = 0 + + for k in range(self.num_rw_ports): + self.add_pin("bl{}".format(port)) + self.add_pin("br{}".format(port)) + self.rw_bl_names.append("bl{}".format(port)) + self.rw_br_names.append("br{}".format(port)) + port += 1 + for k in range(self.num_w_ports): + self.add_pin("bl{}".format(port)) + self.add_pin("br{}".format(port)) + self.w_bl_names.append("bl{}".format(port)) + self.w_br_names.append("br{}".format(port)) + port += 1 + for k in range(self.num_r_ports): + self.add_pin("bl{}".format(port)) + self.add_pin("br{}".format(port)) + self.r_bl_names.append("bl{}".format(port)) + self.r_br_names.append("br{}".format(port)) + port += 1 + + port = 0 + for k in range(self.num_rw_ports): + self.add_pin("wl{}".format(port)) + self.rw_wl_names.append("wl{}".format(port)) + port += 1 + for k in range(self.num_w_ports): + self.add_pin("wl{}".format(port)) + self.w_wl_names.append("wl{}".format(port)) + port += 1 + for k in range(self.num_r_ports): + self.add_pin("wl{}".format(port)) + self.r_wl_names.append("wl{}".format(port)) + port += 1 + + self.add_pin("vdd") + self.add_pin("gnd") + + + def add_modules(self): + """ + Determine size of transistors and add ptx modules + """ + # if there are any read/write ports, then the inverter nmos is sized based the number of read/write ports + if(self.num_rw_ports > 0): + inverter_nmos_width = self.num_rw_ports*3*parameter["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + readwrite_nmos_width = 1.5*parameter["min_tx_size"] + write_nmos_width = parameter["min_tx_size"] + read_nmos_width = 2*parameter["min_tx_size"] + + # 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["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + readwrite_nmos_width = 1.5*parameter["min_tx_size"] + write_nmos_width = parameter["min_tx_size"] + read_nmos_width = 2*parameter["min_tx_size"] + + # create ptx for inverter transistors + self.inverter_nmos = ptx(width=inverter_nmos_width, + tx_type="nmos") + self.add_mod(self.inverter_nmos) + + self.inverter_pmos = ptx(width=inverter_pmos_width, + tx_type="pmos") + self.add_mod(self.inverter_pmos) + + # create ptx for readwrite transitors + self.readwrite_nmos = ptx(width=readwrite_nmos_width, + tx_type="nmos") + self.add_mod(self.readwrite_nmos) + + # create ptx for write transitors + self.write_nmos = ptx(width=write_nmos_width, + tx_type="nmos") + self.add_mod(self.write_nmos) + + # create ptx for read transistors + self.read_nmos = ptx(width=read_nmos_width, + tx_type="nmos") + self.add_mod(self.read_nmos) + + def calculate_spacing(self): + """ Calculate transistor spacings """ + + # calculate metal contact extensions over transistor active + self.inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) + self.readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) + self.write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height) + self.read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height) + + # calculate the distance threshold for different gate contact spacings + self.gate_contact_thres = drc["poly_to_active"] - drc["minwidth_metal2"] + + #calculations for horizontal transistor to tansistor spacing + # inverter spacings + self.inverter_to_inverter_spacing = contact.poly.height + drc["minwidth_metal1"] + self.inverter_to_write_spacing = drc["pwell_to_nwell"] + 2*drc["well_enclosure_active"] + + # readwrite to readwrite transistor spacing (also acts as readwrite to write transistor spacing) + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + self.readwrite_to_readwrite_spacing = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"] + else: + self.readwrite_to_readwrite_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"] + + # write to write transistor spacing + if(self.write_nmos_contact_extension > self.gate_contact_thres): + self.write_to_write_spacing = drc["minwidth_metal2"] + self.write_nmos_contact_extension + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"] + else: + self.write_to_write_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_polycontact"] + drc["poly_extend_active"] + + # read to read transistor spacing + if(self.read_nmos_contact_extension > self.gate_contact_thres): + self.read_to_read_spacing = 2*(drc["minwidth_metal2"] + self.read_nmos_contact_extension) + drc["minwidth_metal1"] + 2*contact.poly.width + else: + self.read_to_read_spacing = 2*drc["poly_to_active"] + drc["minwidth_metal1"] + 2*contact.poly.width + + # write to read transistor spacing (also acts as readwrite to read transistor spacing) + # calculation is dependent on whether the read transistor is adjacent to a write transistor or a readwrite transistor + if(self.num_w_ports > 0): + if(self.write_nmos_contact_extension > self.gate_contact_thres): + write_portion = drc["minwidth_metal2"] + self.write_nmos_contact_extension + else: + write_portion = drc["poly_to_active"] + else: + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + write_portion = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + else: + write_portion = drc["poly_to_active"] + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + read_portion = drc["minwidth_metal2"] + self.read_nmos_contact_extension + else: + read_portion = drc["poly_to_active"] + + self.write_to_read_spacing = write_portion + read_portion + 2*contact.poly.width + drc["poly_to_polycontact"] + + # calculations for transistor tiling (transistor + spacing) + self.inverter_tile_width = self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing + self.readwrite_tile_width = self.readwrite_to_readwrite_spacing + self.readwrite_nmos.active_height + self.write_tile_width = self.write_to_write_spacing + self.write_nmos.active_height + self.read_tile_width = self.read_to_read_spacing + self.read_nmos.active_height + + # calculation for row line tiling + self.rail_tile_height = drc["active_to_body_active"] + contact.well.width + if self.inverter_pmos_contact_extension > 0: + self.vdd_tile_height = self.inverter_pmos_contact_extension + drc["minwidth_metal1"] + contact.well.width + else: + self.vdd_tile_height = self.rail_tile_height + 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_polycontact"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.inverter_pmos_contact_extension + 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_polycontact"] + 1.5*contact.poly.width + + + def calculate_postions(self): + """ + Calculate positions that describe the edges and dimensions of the cell + """ + # create flags for excluding readwrite, write, or read port calculations if they are not included in the bitcell + if(self.num_rw_ports > 0): + self.readwrite_port_flag = True + else: + self.readwrite_port_flag = False + + if(self.num_w_ports > 0): + self.write_port_flag = True + else: + self.write_port_flag = False + + if(self.num_r_ports > 0): + self.read_port_flag = True + else: + self.read_port_flag = False + + # determine the distance of the leftmost/rightmost transistor gate connection + if (self.num_r_ports > 0): + if(self.read_nmos_contact_extension > self.gate_contact_thres): + end_connection = drc["minwidth_metal2"] + self.read_nmos_contact_extension + contact.m1m2.height + else: + end_connection = drc["poly_to_active"] + contact.m1m2.height + else: + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + end_connection = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.m1m2.height + else: + end_connection = drc["poly_to_active"] + contact.m1m2.height + + # leftmost position = storage width + read/write ports width + write ports width + read ports width + end transistor gate connections + metal spacing necessary for tiling the bitcell + self.leftmost_xpos = -self.inverter_tile_width \ + - self.inverter_to_write_spacing \ + - self.readwrite_port_flag*(self.readwrite_nmos.active_height + (self.num_rw_ports-1)*self.readwrite_tile_width) \ + - self.write_port_flag*self.readwrite_port_flag*self.write_to_write_spacing \ + - self.write_port_flag*(self.write_nmos.active_height + (self.num_w_ports-1)*self.write_tile_width) \ + - self.read_port_flag*self.write_to_read_spacing \ + - self.read_port_flag*(self.read_nmos.active_height + (self.num_r_ports-1)*self.read_tile_width) \ + - end_connection \ + - 0.5*drc["poly_to_polycontact"] + + self.rightmost_xpos = -self.leftmost_xpos + + # bottommost position = gnd height + rwwl height + wwl height + rwl height + space needed between tiled bitcells + array_tiling_offset = 0.5*drc["minwidth_metal2"] + self.botmost_ypos = -self.rail_tile_height \ + - self.num_rw_ports*self.rowline_tile_height \ + - self.num_w_ports*self.rowline_tile_height \ + - self.num_r_ports*self.rowline_tile_height \ + - array_tiling_offset + + # topmost position = height of the inverter + height of vdd + self.topmost_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \ + + self.vdd_tile_height + + # calculations for the cell dimensions + array_vdd_overlap = 0.5*contact.well.width + self.width = -2*self.leftmost_xpos + self.height = self.topmost_ypos - self.botmost_ypos - array_vdd_overlap + + + 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(["vdd", "Q", "gnd", "gnd"]) + + self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", + mod=self.inverter_nmos) + self.connect_inst(["gnd", "vdd", "Q", "gnd"]) + + # create active for pmos + self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", + mod=self.inverter_pmos) + self.connect_inst(["vdd", "Q", "vdd", "vdd"]) + + self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", + mod=self.inverter_pmos) + self.connect_inst(["vdd", "vdd", "Q", "vdd"]) + + + def place_storage(self): + """ + Places the transistors for the crossed coupled inverters in the bitcell + """ + + # calculate transistor offsets + left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width + right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing + inverter_pmos_ypos = self.inverter_nmos.active_height + self.inverter_gap + + # create active for nmos + self.inverter_nmos_left.place([left_inverter_xpos,0]) + self.inverter_nmos_right.place([right_inverter_xpos,0]) + + # 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]) + + + def route_storage(self): + """ + Routes inputs and outputs of inverters to cross couple them + """ + # connect input (gate) of inverters + self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()]) + self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()]) + + # connect output (drain/source) of inverters + self.add_path("metal1", [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], width=contact.well.second_layer_width) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.second_layer_width) + + # add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar) + contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, self.cross_couple_upper_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=contact_offset_left, + rotate=90) + + contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=contact_offset_right, + rotate=90) + + # connect contacts to gate poly (cross couple connections) + gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y) + self.add_path("poly", [contact_offset_left, gate_offset_right]) + + gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y) + self.add_path("poly", [contact_offset_right, gate_offset_left]) + + # update furthest left and right transistor edges (this will propagate to further transistor offset calculations) + self.left_building_edge = -self.inverter_tile_width + self.right_building_edge = self.inverter_tile_width + + + def route_rails(self): + """ + Adds gnd and vdd rails and connects them to the inverters + """ + # Add rails for vdd and gnd + self.gnd_position = vector(self.leftmost_xpos, -self.rail_tile_height) + self.gnd = self.add_layout_pin(text="gnd", + layer="metal1", + offset=self.gnd_position, + width=self.width, + height=contact.well.second_layer_width) + + vdd_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \ + + self.vdd_tile_height - contact.well.second_layer_width + self.vdd_position = vector(self.leftmost_xpos, vdd_ypos) + self.vdd = self.add_layout_pin(text="vdd", + layer="metal1", + offset=self.vdd_position, + width=self.width, + height=contact.well.second_layer_width) + + # Connect inverters to rails + # connect inverter nmos to gnd + gnd_pos_left = vector(self.inverter_nmos_left.get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.inverter_nmos_left.get_pin("S").bc(), gnd_pos_left]) + + gnd_pos_right = vector(self.inverter_nmos_right.get_pin("D").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("D").bc(), gnd_pos_right]) + + # connect inverter pmos to vdd + vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y) + self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) + + vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y) + self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) + + + def create_readwrite_ports(self): + """ + Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell. + A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor. + The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q). + In a write operation, driving RWBL high or low sets the value of the cell. + In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell. + This is a differential design, so each write port has a mirrored port that connects RWBL_bar 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 + 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(["Q", self.rw_wl_names[k], self.rw_bl_names[k], "gnd"]) + + self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), + mod=self.readwrite_nmos) + self.connect_inst(["vdd", 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 + self.rwwl_positions = [None] * self.num_rw_ports + self.rwbl_positions = [None] * self.num_rw_ports + self.rwbl_bar_positions = [None] * self.num_rw_ports + + # define offset correction due to rotation of the ptx module + readwrite_rotation_correct = self.readwrite_nmos.active_height + + # iterate over the number of read/write ports + for k in range(0,self.num_rw_ports): + # Add transistors + # calculate read/write transistor offsets + left_readwrite_transistor_xpos = self.left_building_edge \ + - self.inverter_to_write_spacing \ + - self.readwrite_nmos.active_height - k*self.readwrite_tile_width \ + + readwrite_rotation_correct + + right_readwrite_transistor_xpos = self.right_building_edge \ + + self.inverter_to_write_spacing \ + + k*self.readwrite_tile_width \ + + readwrite_rotation_correct + + # add read/write transistors + self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos,0], + rotate=90) + + self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos,0], + rotate=90) + + # Add RWWL lines + # calculate RWWL position + rwwl_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height + self.rwwl_positions[k] = vector(self.leftmost_xpos, rwwl_ypos) + + # add pin for RWWL + self.add_layout_pin(text=self.rw_wl_names[k], + layer="metal1", + offset=self.rwwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + # add pins for RWBL and RWBL_bar, overlaid on source contacts + self.rwbl_positions[k] = vector(self.readwrite_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.rw_bl_names[k], + layer="metal2", + offset=self.rwbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.rwbl_bar_positions[k] = vector(self.readwrite_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.rw_br_names[k], + layer="metal2", + offset=self.rwbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + # update furthest left and right transistor edges + self.left_building_edge = left_readwrite_transistor_xpos - self.readwrite_nmos.active_height + self.right_building_edge = right_readwrite_transistor_xpos + + def route_readwrite_wordlines(self): + """ + Routes read/write trnasistors to their respective wordlines + """ + for k in range(0,self.num_rw_ports): + # Gate/RWWL connections + # add poly-to-meltal2 contacts to connect gate of read/write transistors to RWWL (contact next to gate) + # contact must be placed a metal1 width below the source pin to avoid drc from source pin routings + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.readwrite_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = self.readwrite_nmos_left[k].offset.x - self.readwrite_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width + contact_ypos = self.readwrite_nmos_left[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + left_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_gate_contact) + + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.readwrite_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = self.readwrite_nmos_right[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.width + contact_ypos = self.readwrite_nmos_right[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + right_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_gate_contact) + + # connect gate of read/write transistor to contact (poly path) + midL = vector(left_gate_contact.x, self.readwrite_nmos_left[k].get_pin("G").lc().y) + self.add_path("poly", [self.readwrite_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width) + + midR = vector(right_gate_contact.x, self.readwrite_nmos_right[k].get_pin("G").rc().y) + self.add_path("poly", [self.readwrite_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width) + + # add metal1-to-metal2 contacts to RWWL lines + left_rwwl_contact = vector(left_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_rwwl_contact, + rotate=90) + + right_rwwl_contact = vector(right_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_rwwl_contact, + rotate=90) + + # connect read/write transistor gate contacts to RWWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_rwwl_contact]) + self.add_path("metal2", [right_gate_contact, right_rwwl_contact]) + + + def route_readwrite_bitlines(self): + """ + Routes read/write transistors to their respective bitlines + """ + for k in range(0,self.num_rw_ports): + # Source/RWBL/RWBL_bar connections + # add metal1-to-metal2 contacts on top of read/write transistor source pins for connection to WBL and WBL_bar + offset_left = self.readwrite_nmos_left[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.readwrite_nmos_right[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + + def route_readwrite_access(self): + """ + Routes read/write transistors to the storage component of the bitcell + """ + last_inst = self.num_rw_ports - 1 + + # Drain/Storage connections + # this path only needs to be drawn once on the last iteration of the loop + # add contacts to connect gate of inverters to drain of read/write transistors + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_storage_contact, + rotate=90) + + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_storage_contact, + rotate=90) + + # connect gate of inverters to contacts (poly path) + inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) + + inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [right_storage_contact, inverter_gate_offset_right]) + + # connect contacts to drains of read/write transistors (metal1 path) + midL0 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], left_storage_contact.y) + midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.readwrite_nmos_left[last_inst].get_pin("D").lc().y) + self.add_path("metal1", [left_storage_contact, midL0], width=contact.poly.second_layer_width) # width needed to avoid drc error + self.add_path("metal1", [midL0+vector(0,0.5*contact.poly.second_layer_width), midL1, self.readwrite_nmos_left[last_inst].get_pin("D").lc()]) + + midR0 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], right_storage_contact.y) + midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.readwrite_nmos_right[last_inst].get_pin("D").rc().y) + self.add_path("metal1", [right_storage_contact, midR0], width=contact.poly.second_layer_width) + self.add_path("metal1", [midR0+vector(0,0.5*contact.poly.second_layer_width), midR1, self.readwrite_nmos_right[last_inst].get_pin("D").rc()]) + + def create_write_ports(self): + """ + Creates write ports in the bit cell. A differential pair of transistors can write only. + A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor. + The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q). + In a write operation, driving WBL high or low sets the value of the cell. + This is a differential design, so each write port has a mirrored port that connects WBL_bar 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_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(["Q", self.w_wl_names[k], self.w_bl_names[k], "gnd"]) + + self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), + mod=self.write_nmos) + self.connect_inst(["vdd", 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 + self.wwl_positions = [None] * self.num_w_ports + self.wbl_positions = [None] * self.num_w_ports + self.wbl_bar_positions = [None] * self.num_w_ports + + # 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 + left_write_transistor_xpos = self.left_building_edge \ + - (not self.readwrite_port_flag)*self.inverter_to_write_spacing \ + - (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \ + - self.write_nmos.active_height - k*self.write_tile_width \ + + write_rotation_correct + + right_write_transistor_xpos = self.right_building_edge \ + + (not self.readwrite_port_flag)*self.inverter_to_write_spacing \ + + (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \ + + k*self.write_tile_width \ + + write_rotation_correct + + # add write transistors + self.write_nmos_left[k].place(offset=[left_write_transistor_xpos,0], + rotate=90) + + self.write_nmos_right[k].place(offset=[right_write_transistor_xpos,0], + rotate=90) + + # Add WWL lines + # calculate WWL position + wwl_ypos = self.gnd_position.y \ + - self.num_rw_ports*self.rowline_tile_height \ + - (k+1)*self.rowline_tile_height + self.wwl_positions[k] = vector(self.leftmost_xpos, wwl_ypos) + + # add pin for WWL + self.add_layout_pin(text=self.w_wl_names[k], + layer="metal1", + offset=self.wwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + # add pins for WBL and WBL_bar, overlaid on source contacts + self.wbl_positions[k] = vector(self.write_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.w_bl_names[k], + layer="metal2", + offset=self.wbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.wbl_bar_positions[k] = vector(self.write_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.w_br_names[k], + layer="metal2", + offset=self.wbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + # update furthest left and right transistor edges + self.left_building_edge = left_write_transistor_xpos - self.write_nmos.active_height + self.right_building_edge = right_write_transistor_xpos + + def route_write_wordlines(self): + """ + Routes write transistors to their respective wordlines + """ + for k in range(0,self.num_w_ports): + # Gate/WWL connections + # add poly-to-meltal2 contacts to connect gate of write transistors to WWL (contact next to gate) + # contact must be placed a metal width below the source pin to avoid drc from source pin routings + if(self.write_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.write_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = self.write_nmos_left[k].offset.x - self.write_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.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) + + if(self.write_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.write_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = self.write_nmos_right[k].offset.x + drc["poly_to_active"] + 0.5*contact.poly.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) + + # connect gate of write transistor to contact (poly path) + midL = vector(left_gate_contact.x, self.write_nmos_left[k].get_pin("G").lc().y) + self.add_path("poly", [self.write_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width) + + midR = vector(right_gate_contact.x, self.write_nmos_right[k].get_pin("G").rc().y) + self.add_path("poly", [self.write_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width) + + # add metal1-to-metal2 contacts to WWL lines + left_wwl_contact = vector(left_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_wwl_contact, + rotate=90) + + right_wwl_contact = vector(right_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_wwl_contact, + rotate=90) + + # connect write transistor gate contacts to WWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_wwl_contact]) + self.add_path("metal2", [right_gate_contact, right_wwl_contact]) + + def route_write_bitlines(self): + """ + Routes write transistors to their respective bitlines + """ + for k in range(0,self.num_w_ports): + # Source/WBL/WBL_bar connections + # add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar + offset_left = self.write_nmos_left[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.write_nmos_right[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + def route_write_access(self): + """ + Routes write transistors to the storage component of the bitcell + """ + last_inst = self.num_w_ports - 1 + + # Drain/Storage connections + # this path only needs to be drawn once on the last iteration of the loop + # add contacts to connect gate of inverters to drain of write transistors + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_polycontact"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_storage_contact, + rotate=90) + + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_polycontact"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_storage_contact, + rotate=90) + + # connect gate of inverters to contacts (poly path) + inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) + + inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [right_storage_contact, inverter_gate_offset_right]) + + # connect contacts to drains of write transistors (metal1 path) + midL0 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], left_storage_contact.y) + midL1 = vector(self.inverter_nmos_left.get_pin("S").lc().x - 1.5*drc["minwidth_metal1"], self.write_nmos_left[last_inst].get_pin("D").lc().y) + self.add_path("metal1", [left_storage_contact, midL0], width=contact.poly.second_layer_width) # width needed to avoid drc error + self.add_path("metal1", [midL0+vector(0,0.5*contact.poly.second_layer_width), midL1, self.write_nmos_left[last_inst].get_pin("D").lc()]) + + midR0 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], right_storage_contact.y) + midR1 = vector(self.inverter_nmos_right.get_pin("D").rc().x + 1.5*drc["minwidth_metal1"], self.write_nmos_right[last_inst].get_pin("D").rc().y) + self.add_path("metal1", [right_storage_contact, midR0], width=contact.poly.second_layer_width) + self.add_path("metal1", [midR0+vector(0,0.5*contact.poly.second_layer_width), midR1, self.write_nmos_right[last_inst].get_pin("D").rc()]) + + + def create_read_ports(self): + """ + Creates read ports in the bit cell. A differential pair of ports can read only. + Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor". + The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source). + The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain). + A read is enabled by setting a Read-Rowline (RWL) high, subsequently turning on the read transistor. + The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor + is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read + using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q. + """ + + # define read transistor variables as empty arrays based on the number of read ports + self.read_nmos_left = [None] * self.num_r_ports + self.read_nmos_right = [None] * self.num_r_ports + self.read_access_nmos_left = [None] * self.num_r_ports + self.read_access_nmos_right = [None] * self.num_r_ports + + # iterate over the number of read ports + for k in range(0,self.num_r_ports): + # add read-access transistors + self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k), + mod=self.read_nmos) + self.connect_inst(["RA_to_R_left{}".format(k), "vdd", "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(["RA_to_R_right{}".format(k), "Q", "gnd", "gnd"]) + + # add read transistors + self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), + mod=self.read_nmos) + 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([self.r_br_names[k], self.r_wl_names[k], "RA_to_R_right{}".format(k), "gnd"]) + + def place_read_ports(self): + """ + Places the read ports in the bit cell. + """ + # Define variables relevant to read transistors + self.rwl_positions = [None] * self.num_r_ports + self.rbl_positions = [None] * self.num_r_ports + self.rbl_bar_positions = [None] * self.num_r_ports + + # define offset correction due to rotation of the ptx module + read_rotation_correct = self.read_nmos.active_height + + # calculate offset to overlap the drain of the read-access transistor with the source of the read transistor + overlap_offset = self.read_nmos.get_pin("D").ll() - self.read_nmos.get_pin("S").ll() + + # iterate over the number of read ports + for k in range(0,self.num_r_ports): + # Add transistors + # calculate transistor offsets + left_read_transistor_xpos = self.left_building_edge \ + - self.write_to_read_spacing \ + - self.read_nmos.active_height - k*self.read_tile_width \ + + read_rotation_correct + + right_read_transistor_xpos = self.right_building_edge \ + + self.write_to_read_spacing \ + + k*self.read_tile_width \ + + read_rotation_correct + + # add read-access transistors + self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos,0], + rotate=90) + + self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos,0], + rotate=90) + + # add read transistors + self.read_nmos_left[k].place(offset=[left_read_transistor_xpos,overlap_offset.x], + rotate=90) + + self.read_nmos_right[k].place(offset=[right_read_transistor_xpos,overlap_offset.x], + rotate=90) + + # Add RWL lines + # calculate RWL position + rwl_ypos = self.gnd_position.y \ + - self.num_rw_ports*self.rowline_tile_height \ + - self.num_w_ports*self.rowline_tile_height \ + - (k+1)*self.rowline_tile_height + self.rwl_positions[k] = vector(self.leftmost_xpos, rwl_ypos) + + # add pin for RWL + self.add_layout_pin(text=self.r_wl_names[k], + layer="metal1", + offset=self.rwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + # add pins for RBL and RBL_bar, overlaid on drain contacts + self.rbl_positions[k] = vector(self.read_nmos_left[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.r_bl_names[k], + layer="metal2", + offset=self.rbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.rbl_bar_positions[k] = vector(self.read_nmos_right[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text=self.r_br_names[k], + layer="metal2", + offset=self.rbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + def route_read_wordlines(self): + """ + Routes read transistors to their respective worlines + """ + for k in range(0,self.num_r_ports): + # Gate of read transistor / RWL connection + # add poly-to-meltal2 contacts to connect gate of read transistors to RWL (contact next to gate) + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = self.read_nmos_left[k].offset.x - self.read_nmos.active_height - drc["poly_to_active"] - 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) + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = self.read_nmos_right[k].offset.x + drc["poly_to_active"] + 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 RWL lines + left_rwl_contact = vector(left_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_rwl_contact, + rotate=90) + + right_rwl_contact = vector(right_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_rwl_contact, + rotate=90) + + # connect read transistor gate contacts to RWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_rwl_contact]) + self.add_path("metal2", [right_gate_contact, right_rwl_contact]) + + # Source of read-access transistor / GND connection + # connect source of read-access transistor to GND (metal1 path) + gnd_offset_left = vector(self.read_access_nmos_left[k].get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.read_access_nmos_left[k].get_pin("S").bc(), gnd_offset_left]) + + gnd_offset_right = vector(self.read_access_nmos_right[k].get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.read_access_nmos_right[k].get_pin("S").bc(), gnd_offset_right]) + + def route_read_bitlines(self): + """ + Routes read transistors to their respective bitlines + """ + for k in range(0,self.num_r_ports): + # Drain of read transistor / RBL & RBL_bar connection + # add metal1-to-metal2 contacts on top of read transistor drain pins for connection to RBL and RBL_bar + offset_left = self.read_nmos_left[k].get_pin("D").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.read_nmos_right[k].get_pin("D").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + def route_read_access(self): + """ + Routes read access transistors to the storage component of the bitcell + """ + for k in range(0,self.num_r_ports): + # 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) + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_left[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = self.read_nmos_left[k].offset.x + drc["poly_to_active"] + 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) + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_right[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = self.read_nmos_right[k].offset.x - self.read_nmos.active_height - drc["poly_to_active"] - 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]) + + # save the positions of the first gate contacts for use in later iterations + if(k == 0): + left_gate_contact0 = left_gate_contact + right_gate_contact0 = right_gate_contact + + # connect contact to output of inverters (metal1 path) + # mid0: metal1 path must route over the read transistors (above drain of read transistor) + # mid1: continue metal1 path horizontally until at first read access gate contact + # mid2: route up or down to be level with inverter output + # endpoint at drain/source of inverter + midL0 = vector(left_gate_contact.x, self.read_nmos_left[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midL1 = vector(left_gate_contact0.x, self.read_nmos_left[0].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midL2 = vector(left_gate_contact0.x, self.cross_couple_upper_ypos) + left_inverter_offset = vector(self.inverter_nmos_left.get_pin("D").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [left_gate_contact, midL0, midL1, midL2, left_inverter_offset]) + + midR0 = vector(right_gate_contact.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR1 = vector(right_gate_contact0.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR2 = vector(right_gate_contact0.x, self.cross_couple_upper_ypos) + right_inverter_offset = vector(self.inverter_nmos_right.get_pin("S").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [right_gate_contact, midR0, midR1, midR2, right_inverter_offset]) + + 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. + """ + + # 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.width, + height=well_height) + + # extend pwell over read/write and write transistors to the + # height of the write transistor well (read/write and write + # transistors are the same height) + if(self.num_w_ports > 0): + # calculate the edge of the write transistor well closest to the center + left_write_well_xpos = self.write_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_write_well_xpos = self.write_nmos_right[0].offset.x - self.write_nmos.active_height - drc["well_enclosure_active"] + else: + # calculate the edge of the read/write transistor well closest to the center + left_write_well_xpos = self.readwrite_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_write_well_xpos = self.readwrite_nmos_right[0].offset.x - self.readwrite_nmos.active_height - drc["well_enclosure_active"] + + # calculate a width that will halt at the edge of the write transistors + write_well_width = -(self.leftmost_xpos - left_write_well_xpos) + write_well_height = self.write_nmos.cell_well_width - drc["well_enclosure_active"] + + offset = vector(left_write_well_xpos - write_well_width, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + offset = vector(right_write_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + # extend pwell over the read transistors to the height of the bitcell + if(self.num_r_ports > 0): + # calculate the edge of the read transistor well clostest to the center + left_read_well_xpos = self.read_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_read_well_xpos = self.read_nmos_right[0].offset.x - self.read_nmos.active_height - drc["well_enclosure_active"] + + # calculate a width that will halt at the edge of the read transistors + read_well_width = -(self.leftmost_xpos - left_read_well_xpos) + read_well_height = self.topmost_ypos + + offset = vector(self.leftmost_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + offset = vector(right_read_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + # extend nwell to encompass inverter_pmos + # calculate offset of the left pmos well + inverter_well_xpos = -self.inverter_tile_width - drc["well_enclosure_active"] + inverter_well_ypos = self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"] + + # calculate width of the two combined nwells + # calculate height to encompass nimplant connected to vdd + well_width = 2*self.inverter_tile_width + 2*drc["well_enclosure_active"] + well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"] + + offset = [inverter_well_xpos,inverter_well_ypos] + self.add_rect(layer="nwell", + offset=offset, + width=well_width, + height=well_height) + + + # 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, + implant_type="p", + well_type="p") + + # connect nimplants to vdd + offset = vector(0, self.vdd_position.y + 0.5*contact.well.second_layer_width) + self.add_contact_center(layers=("active", "contact", "metal1"), + offset=offset, + rotate=90, + implant_type="n", + well_type="n") + + + def route_rbc_short(self): + """ route the short from Q_bar to gnd necessary for the replica bitcell """ + Q_bar_pos = self.inverter_pmos_left.get_pin("D").uc() + vdd_pos = vector(Q_bar_pos.x, self.vdd_position.y) + + self.add_path("metal1", [Q_bar_pos, vdd_pos]) \ No newline at end of file diff --git a/compiler/tests/04_replica_pbitcell_test.py b/compiler/tests/04_replica_pbitcell_test.py new file mode 100644 index 00000000..65f6a6e6 --- /dev/null +++ b/compiler/tests/04_replica_pbitcell_test.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +""" +Run a regression test on a replica pbitcell +""" + +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 + +class replica_pbitcell_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + import replica_pbitcell + import tech + + # check precharge in multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 0 + OPTS.num_w_ports = 0 + + debug.info(2, "Checking replica bitcell using pbitcell (small cell)") + tx = replica_pbitcell.replica_pbitcell() + self.local_check(tx) + + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + debug.info(2, "Checking replica bitcell using pbitcell (large cell)") + tx = replica_pbitcell.replica_pbitcell() + self.local_check(tx) + + 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/14_replica_bitline_test.py b/compiler/tests/14_replica_bitline_test.py old mode 100755 new mode 100644 index 6ecd612a..0eaedc15 --- a/compiler/tests/14_replica_bitline_test.py +++ b/compiler/tests/14_replica_bitline_test.py @@ -17,6 +17,27 @@ class replica_bitline_test(openram_test): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) import replica_bitline + # check replica bitline in single port + stages=4 + fanout=4 + rows=13 + debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) + a = replica_bitline.replica_bitline(stages,fanout,rows) + self.local_check(a) + + stages=8 + rows=100 + debug.info(2, "Testing RBL with {0} FO4 stages, {1} rows".format(stages,rows)) + a = replica_bitline.replica_bitline(stages,fanout,rows) + self.local_check(a) + + # check replica bitline in multi-port + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell = "replica_pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + stages=4 fanout=4 rows=13