From c8c70401ae0dfafe753aeae341ae7731beb9fdb9 Mon Sep 17 00:00:00 2001 From: Michael Timothy Grimes Date: Mon, 15 Oct 2018 06:29:51 -0700 Subject: [PATCH] Redesign of pbitcell for newer process technolgies. --- compiler/base/design.py | 1 + compiler/pgates/pbitcell.py | 1045 ++++++++++++----------------------- 2 files changed, 367 insertions(+), 679 deletions(-) diff --git a/compiler/base/design.py b/compiler/base/design.py index a3f4dbbf..cbe4f3cf 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -43,6 +43,7 @@ class design(hierarchy_design): self.poly_to_active = drc["poly_to_active"] self.poly_extend_active = drc["poly_extend_active"] + self.poly_to_polycontact = drc["poly_to_polycontact"] self.contact_to_gate = drc["contact_to_gate"] self.well_enclose_active = drc["well_enclosure_active"] self.implant_enclose_active = drc["implant_enclosure_active"] diff --git a/compiler/pgates/pbitcell.py b/compiler/pgates/pbitcell.py index 9414ba2d..2cb63b8e 100644 --- a/compiler/pgates/pbitcell.py +++ b/compiler/pgates/pbitcell.py @@ -36,7 +36,6 @@ class pbitcell(design.design): # some transistor sizes in the other netlists depend on it self.create_layout() - def create_netlist(self): self.add_pins() self.add_modules() @@ -55,26 +54,24 @@ class pbitcell(design.design): 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() + 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_wordlines() + self.route_bitlines() + self.route_supply() + if self.replica_bitcell: self.route_rbc_short() @@ -82,6 +79,8 @@ class pbitcell(design.design): # 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): @@ -136,7 +135,6 @@ class pbitcell(design.design): self.Q_bar = "vdd" else: self.Q_bar = "Q_bar" - def add_modules(self): """ @@ -184,141 +182,63 @@ class pbitcell(design.design): 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) + 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) - # calculate the distance threshold for different gate contact spacings - self.gate_contact_thres = drc["poly_to_active"] - drc["minwidth_metal2"] + # 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) - #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"] + # y-position of access transistors + self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset - # 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"] + # y-position of inverter nmos + self.inverter_nmos_ypos = self.port_ypos - # 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"] + # spacing between ports + 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 - # 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 + # spacing between cross coupled inverters + self.inverter_to_inverter_spacing = contact.poly.height + self.m1_space # 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 + inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) + self.inverter_gap = self.poly_to_active + self.poly_to_polycontact + 2*contact.poly.width + self.m1_space + inverter_pmos_contact_extension + self.cross_couple_lower_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.poly_to_active + 0.5*contact.poly.width + self.cross_couple_upper_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.poly_to_active + self.poly_to_polycontact + 1.5*contact.poly.width + # spacing between wordlines (and gnd) + self.rowline_spacing = self.m1_space + contact.m1m2.width + + # spacing for vdd + vdd_offset_well_constraint = self.well_enclose_active + 0.5*contact.well.width + vdd_offset_metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space + 0.5*contact.well.width + self.vdd_offset = max(vdd_offset_well_constraint, vdd_offset_metal1_constraint) + + # 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 """ - # 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 + 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 - 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 + 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_space - # 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 - + self.height = self.topmost_ypos - self.botmost_ypos + + self.y_center = 0.5*(self.topmost_ypos + self.botmost_ypos) def create_storage(self): """ @@ -329,21 +249,20 @@ class pbitcell(design.design): # create active for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", mod=self.inverter_nmos) - self.connect_inst([self.Q_bar, "Q", "gnd", "gnd"]) + 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", self.Q_bar, "Q", "gnd"]) + 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([self.Q_bar, "Q", "vdd", "vdd"]) + 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", self.Q_bar, "Q", "vdd"]) - + self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"]) def place_storage(self): """ @@ -353,16 +272,18 @@ class pbitcell(design.design): # 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 + 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,0]) - self.inverter_nmos_right.place([right_inverter_xpos,0]) + 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]) - + + self.left_building_edge = left_inverter_xpos + self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width def route_storage(self): """ @@ -394,47 +315,26 @@ class pbitcell(design.design): 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) + gnd_ypos = -0.5*self.m1_width - 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=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]) - + 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", + layer="metal1", + offset=self.vdd_position, + width=self.width, + height=contact.well.second_layer_width) def create_readwrite_ports(self): """ @@ -455,12 +355,11 @@ class pbitcell(design.design): # 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.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): """ @@ -470,172 +369,57 @@ class pbitcell(design.design): # 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 + 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 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 + - (k+1)*self.port_spacing \ + - (k+1)*self.readwrite_nmos.active_width right_readwrite_transistor_xpos = self.right_building_edge \ - + self.inverter_to_write_spacing \ - + k*self.readwrite_tile_width \ - + readwrite_rotation_correct + + (k+1)*self.port_spacing \ + + k*self.readwrite_nmos.active_width # add read/write transistors - self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos,0], - rotate=90) + 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,0], - rotate=90) + self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos]) # 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) + rwwl_ypos = -0.5*self.m1_width - k*self.rowline_spacing + self.rwwl_positions[k] = vector(0, 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) + 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 - 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) + rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + self.m2_width + self.rwbl_positions[k] = vector(rwbl_xpos, self.y_center) + self.add_layout_pin_rect_center(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) + 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.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 - 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()]) + 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): """ @@ -659,12 +443,11 @@ class pbitcell(design.design): # 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.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): """ @@ -673,7 +456,7 @@ class pbitcell(design.design): # 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 + 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 @@ -683,165 +466,50 @@ class pbitcell(design.design): # 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 + - (k+1)*self.port_spacing \ + - (k+1)*self.write_nmos.active_width 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 + + (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,0], - rotate=90) + 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,0], - rotate=90) + self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos]) # 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) + 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 - self.add_layout_pin(text=self.w_wl_names[k], - layer="metal1", - offset=self.wwl_positions[k], - width=self.width, - height=contact.m1m2.width) + 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 - 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) + wbl_xpos = left_write_transistor_xpos - self.bitline_offset + self.m2_width + self.wbl_positions[k] = vector(wbl_xpos, self.y_center) + self.add_layout_pin_rect_center(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) + 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.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 - 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()]) - + 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): """ @@ -870,7 +538,7 @@ class pbitcell(design.design): 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"]) + 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), @@ -879,7 +547,7 @@ class pbitcell(design.design): 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"]) + 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): """ @@ -888,199 +556,262 @@ class pbitcell(design.design): # 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 + 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").ll() - self.read_nmos.get_pin("S").ll() + 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 \ - - self.write_to_read_spacing \ - - self.read_nmos.active_height - k*self.read_tile_width \ - + read_rotation_correct - + - (k+1)*self.port_spacing \ + - (k+1)*self.read_port_width + right_read_transistor_xpos = self.right_building_edge \ - + self.write_to_read_spacing \ - + k*self.read_tile_width \ - + read_rotation_correct + + (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,0], - rotate=90) + 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,0], - rotate=90) + 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,overlap_offset.x], - rotate=90) + 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.x], - rotate=90) + self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos]) # 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) + 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 - self.add_layout_pin(text=self.r_wl_names[k], - layer="metal1", - offset=self.rwl_positions[k], - width=self.width, - height=contact.m1m2.width) + 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 - 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) + rbl_xpos = left_read_transistor_xpos - self.bitline_offset + self.m2_width + self.rbl_positions[k] = vector(rbl_xpos, self.y_center) + self.add_layout_pin_rect_center(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) + 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.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 + """ + port_transistors = [] + for k in range(self.num_rw_ports): + port_transistors.append(self.readwrite_nmos_left[k]) + port_transistors.append(self.readwrite_nmos_right[k]) + for k in range(self.num_w_ports): + port_transistors.append(self.write_nmos_left[k]) + port_transistors.append(self.write_nmos_right[k]) + 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]) + wl_positions.append(self.rwwl_positions[k]) + for k in range(self.num_w_ports): + wl_positions.append(self.wwl_positions[k]) + wl_positions.append(self.wwl_positions[k]) + for k in range(self.num_r_ports): + wl_positions.append(self.rwl_positions[k]) + wl_positions.append(self.rwl_positions[k]) - 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 + + 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) + + 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("metal1", [port_contact_offset, wl_contact_offset]) + 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): + self.add_contact_center(layers=("poly", "contact", "metal1"), + 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("metal2", [port_contact_offset, wl_contact_offset]) + + def route_bitlines(self): + """ + Routes read/write transistors to their respective bitlines """ - 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) + left_port_transistors = [] + right_port_transistors = [] + for k in range(self.num_rw_ports): + left_port_transistors.append(self.readwrite_nmos_left[k]) + right_port_transistors.append(self.readwrite_nmos_right[k]) + for k in range(self.num_w_ports): + left_port_transistors.append(self.write_nmos_left[k]) + right_port_transistors.append(self.write_nmos_right[k]) + 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): + bl_positions.append(self.rwbl_positions[k]) + br_positions.append(self.rwbr_positions[k]) + for k in range(self.num_w_ports): + bl_positions.append(self.wbl_positions[k]) + br_positions.append(self.wbr_positions[k]) + 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) - offset_right = self.read_nmos_right[k].get_pin("D").center() self.add_contact_center(layers=("metal1", "via1", "metal2"), - offset=offset_right, - rotate=90) + 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 + 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 + """ + 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 + """ + 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 """ - 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) - + 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]) + + 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=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) + 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=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]) + 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): """ @@ -1088,73 +819,29 @@ class pbitcell(design.design): 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. """ + + 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) + + 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) - 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"] + 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_tile_width + 2*drc["well_enclosure_active"] + 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] @@ -1166,7 +853,7 @@ class pbitcell(design.design): # add well contacts # connect pimplants to gnd - offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width) + offset = vector(0, self.gnd_position.y) self.add_contact_center(layers=("active", "contact", "metal1"), offset=offset, rotate=90, @@ -1174,14 +861,13 @@ class pbitcell(design.design): well_type="p") # connect nimplants to vdd - offset = vector(0, self.vdd_position.y + 0.5*contact.well.second_layer_width) + 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") - 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 = [] @@ -1243,3 +929,4 @@ class pbitcell(design.design): 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