diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 51efc498..1ffdc7bf 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -42,6 +42,8 @@ class contact(hierarchy_design.hierarchy_design): if implant_type or well_type: self.add_comment("implant type: {}\n".format(implant_type)) self.add_comment("well_type: {}\n".format(well_type)) + + self.is_well_contact = implant_type == well_type self.layer_stack = layer_stack self.dimensions = dimensions @@ -114,7 +116,12 @@ class contact(hierarchy_design.hierarchy_design): self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name)) self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name)) - self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) + # If there's a different rule for active + # FIXME: Make this more elegant + if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys(): + self.first_layer_extend = drc("tap_extend_contact") + else: + self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)) self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name)) self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name)) @@ -231,14 +238,18 @@ class contact(hierarchy_design.hierarchy_design): # Optionally implant well if layer exists well_layer = "{}well".format(self.well_type) if well_layer in tech.layer: - well_enclose_active = drc(well_layer + "_enclose_active") - well_position = self.first_layer_position - [well_enclose_active] * 2 - well_width = self.first_layer_width + 2 * well_enclose_active - well_height = self.first_layer_height + 2 * well_enclose_active + well_width_rule = drc("minwidth_" + well_layer) + self.well_enclose_active = drc(well_layer + "_enclose_active") + self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active, + well_width_rule) + self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active, + well_width_rule) + center_pos = vector(0.5*self.width, 0.5*self.height) + well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height) self.add_rect(layer=well_layer, offset=well_position, - width=well_width, - height=well_height) + width=self.well_width, + height=self.well_height) def analytical_power(self, corner, load): """ Get total power of a module """ @@ -257,6 +268,21 @@ for layer_stack in tech.layer_stacks: else: setattr(module, layer1 + "_via", cont) - +# Set up a static for each well contact for measurements +if "nwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="n", + well_type="n") + module = sys.modules[__name__] + setattr(module, "nwell_contact", cont) + +if "pwell" in tech.layer: + cont = factory.create(module_type="contact", + layer_stack=tech.active_stack, + implant_type="p", + well_type="p") + module = sys.modules[__name__] + setattr(module, "pwell_contact", cont) diff --git a/compiler/base/design.py b/compiler/base/design.py index 9db68877..c2ef8340 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -113,6 +113,15 @@ class design(hierarchy_design): if match: setattr(self, match.group(0), drc(match.group(0))) + # Create the maximum well extend active that gets used + # by cells to extend the wells for interaction with other cells + from tech import layer + self.well_extend_active = 0 + if "nwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active) + if "pwell" in layer: + self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active) + # These are for debugging previous manual rules if False: print("poly_width", self.poly_width) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 4a674c23..133a66c2 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -5,19 +5,15 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, parameter import debug import design -import math -from math import log,sqrt,ceil -import contact -import pgates from sram_factory import factory +from math import log +from tech import drc from vector import vector - from globals import OPTS + class bank(design.design): """ Dynamically generated a single bank including bitcell array, @@ -500,6 +496,8 @@ class bank(design.design): Create a 2:4 or 3:8 column address decoder. """ + # Height is a multiple of DFF so that it can be staggered + # and rows do not align with the control logic module self.dff = factory.create(module_type="dff") if self.col_addr_size == 0: @@ -868,7 +866,6 @@ class bank(design.design): route_map = list(zip(decode_pins, column_mux_pins)) self.create_vertical_channel_route(route_map, offset, self.m1_stack) - def add_lvs_correspondence_points(self): """ This adds some points for easier debugging if LVS goes wrong. These should probably be turned off by default though, since extraction @@ -913,7 +910,6 @@ class bank(design.design): layer="m1", offset=data_pin.center()) - def route_control_lines(self, port): """ Route the control lines of the entire bank """ @@ -921,23 +917,25 @@ class bank(design.design): # From control signal to the module pin # Connection from the central bus to the main control block crosses # pre-decoder and this connection is in metal3 - write_inst = 0 - read_inst = 0 - connection = [] - connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc())) + connection.append((self.prefix + "p_en_bar{}".format(port), + self.port_data_inst[port].get_pin("p_en_bar").lc())) rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]) - connection.append((self.prefix+"wl_en{}".format(port), self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) + connection.append((self.prefix + "wl_en{}".format(port), + self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) if port in self.write_ports: if port % 2: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").rc())) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en").rc())) else: - connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").lc())) + connection.append((self.prefix + "w_en{}".format(port), + self.port_data_inst[port].get_pin("w_en").lc())) if port in self.read_ports: - connection.append((self.prefix+"s_en{}".format(port), self.port_data_inst[port].get_pin("s_en").lc())) + connection.append((self.prefix + "s_en{}".format(port), + self.port_data_inst[port].get_pin("s_en").lc())) for (control_signal, pin_pos) in connection: control_mid_pos = self.bus_xoffset[port][control_signal] @@ -961,7 +959,7 @@ class bank(design.design): self.add_via_center(layers=self.m1_stack, offset=control_pos) - def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): + def determine_wordline_stage_efforts(self, external_cout, inp_is_rise=True): """Get the all the stage efforts for each stage in the path within the bank clk_buf to a wordline""" #Decoder is assumed to have settled before the negative edge of the clock. Delay model relies on this assumption stage_effort_list = [] diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 1fba563b..6f06b778 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -50,10 +50,11 @@ class dff_buf(design.design): self.create_instances() def create_layout(self): - self.width = self.dff.width + self.inv1.width + self.inv2.width + self.place_instances() + self.width = self.inv2_inst.rx() self.height = self.dff.height - self.place_instances() + self.route_wires() self.add_layout_pins() self.add_boundary() @@ -106,7 +107,10 @@ class dff_buf(design.design): self.dff_inst.place(vector(0,0)) # Add INV1 to the right - self.inv1_inst.place(vector(self.dff_inst.rx(),0)) + well_spacing = max(self.nwell_space, + self.pwell_space, + self.pwell_to_nwell) + self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active,0)) # Add INV2 to the right self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 770675bf..1735a7a1 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -75,6 +75,7 @@ class dff_buf_array(design.design): inv2_size=self.inv2_size) self.add_mod(self.dff) + def create_dff_array(self): self.dff_insts={} for row in range(self.rows): @@ -94,14 +95,21 @@ class dff_buf_array(design.design): self.connect_inst(inst_ports) def place_dff_array(self): + + well_spacing = max(self.nwell_space, + self.pwell_space, + self.pwell_to_nwell) + + dff_pitch = self.dff.width + well_spacing + self.well_extend_active + for row in range(self.rows): for col in range(self.columns): name = "Xdff_r{0}_c{1}".format(row,col) if (row % 2 == 0): - base = vector(col*self.dff.width,row*self.dff.height) + base = vector(col*dff_pitch,row*self.dff.height) mirror = "R0" else: - base = vector(col*self.dff.width,(row+1)*self.dff.height) + base = vector(col*dff_pitch,(row+1)*self.dff.height) mirror = "MX" self.dff_insts[row,col].place(offset=base, mirror=mirror) diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index f83035ce..242ef476 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -5,18 +5,14 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc import debug import design -from math import log -from math import sqrt -from math import ceil import math -import contact from sram_factory import factory from vector import vector from globals import OPTS + class hierarchical_decoder(design.design): """ Dynamically generated hierarchical decoder. @@ -34,13 +30,12 @@ class hierarchical_decoder(design.design): self.cell_height = b.height self.rows = rows self.num_inputs = math.ceil(math.log(self.rows, 2)) - (self.no_of_pre2x4,self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) + (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) self.create_netlist() if not OPTS.netlist_only: self.create_layout() - def create_netlist(self): self.add_modules() self.setup_netlist_constants() @@ -308,7 +303,6 @@ class hierarchical_decoder(design.design): base= vector(-self.pre2_4.width, num * self.pre2_4.height) self.pre2x4_inst[num].place(base) - def place_pre3x8(self,num): """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ @@ -321,7 +315,6 @@ class hierarchical_decoder(design.design): self.pre3x8_inst[num].place(offset) - def create_row_decoder(self): """ Create the row-decoder by placing NAND2/NAND3 and Inverters and add the primary decoder output pins. """ @@ -329,7 +322,6 @@ class hierarchical_decoder(design.design): self.create_decoder_nand_array() self.create_decoder_inv_array() - def create_decoder_nand_array(self): """ Add a column of NAND gates for final decode """ @@ -556,7 +548,7 @@ class hierarchical_decoder(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # The vias will be placed in the center and right of the cells, respectively. - xoffset = self.nand_inst[0].cx() + xoffset = self.nand_inst[0].rx() for num in range(0,self.rows): for pin_name in ["vdd", "gnd"]: # The nand and inv are the same height rows... diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 78e9af7f..88bae534 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -8,12 +8,11 @@ import debug import design import math -from tech import drc import contact from vector import vector -from globals import OPTS from sram_factory import factory + class hierarchical_predecode(design.design): """ Pre 2x4 and 3x8 decoder shared code. @@ -42,7 +41,7 @@ class hierarchical_predecode(design.design): self.add_nand(self.number_of_inputs) self.add_mod(self.nand) - def add_nand(self,inputs): + def add_nand(self, inputs): """ Create the NAND for the predecode input stage """ if inputs==2: self.nand = factory.create(module_type="pnand2", @@ -51,7 +50,7 @@ class hierarchical_predecode(design.design): self.nand = factory.create(module_type="pnand3", height=self.cell_height) else: - debug.error("Invalid number of predecode inputs: {}".format(inputs),-1) + debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) def setup_layout_constraints(self): @@ -89,7 +88,6 @@ class hierarchical_predecode(design.design): names=decode_names, length=self.height - 2*self.m1_width) - def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ self.in_inst = [] @@ -266,7 +264,7 @@ class hierarchical_predecode(design.design): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ # Find the x offsets for where the vias/pins should be placed - in_xoffset = self.in_inst[0].rx() + in_xoffset = self.in_inst[0].rx() + self.m1_space out_xoffset = self.inv_inst[0].lx() - self.m1_space for num in range(0,self.number_of_outputs): # this will result in duplicate polygons for rails, but who cares diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index cac2ee9d..6d385d09 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -11,7 +11,6 @@ import debug from tech import layer from vector import vector from globals import OPTS -from sram_factory import factory class pgate(design.design): @@ -29,7 +28,7 @@ class pgate(design.design): elif not height: # By default, we make it 8 M1 pitch tall self.height = 8*self.m1_pitch - + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -75,11 +74,13 @@ class pgate(design.design): pmos_gate_pin = pmos_inst.get_pin("G") # Check if the gates are aligned and give an error if they aren't! + if nmos_gate_pin.ll().x != pmos_gate_pin.ll().x: + self.gds_write("unaliged_gates.gds") debug.check(nmos_gate_pin.ll().x == pmos_gate_pin.ll().x, - "Connecting unaligned gates not supported.") + "Connecting unaligned gates not supported. See unaligned_gates.gds.") - # Pick point on the left of NMOS and connect down to PMOS - nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5 * self.poly_width, 0) + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.ul() + vector(0.5 * self.poly_width, 0) pmos_gate_pos = vector(nmos_gate_pos.x, pmos_gate_pin.bc().y) self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) @@ -123,19 +124,22 @@ class pgate(design.design): height=contact.poly_contact.first_layer_width, width=left_gate_offset.x - contact_offset.x) - def extend_wells(self, middle_position): + def extend_wells(self): """ Extend the n/p wells to cover whole cell """ + # This should match the cells in the cell library + nwell_y_offset = 0.48 * self.height + full_height = self.height + 0.5*self.m1_width + # FIXME: float rounding problem - middle_position = middle_position.snap_to_grid() if "nwell" in layer: # Add a rail width to extend the well to the top of the rail nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, - self.height + 0.5 * self.m1_width) - nwell_position = middle_position - nwell_height = nwell_max_offset - middle_position.y + full_height) + nwell_position = vector(0, nwell_y_offset) - vector(self.well_extend_active, 0) + nwell_height = nwell_max_offset - nwell_y_offset self.add_rect(layer="nwell", - offset=middle_position, + offset=nwell_position, width=self.well_width, height=nwell_height) if "vtg" in layer: @@ -148,8 +152,8 @@ class pgate(design.design): if "pwell" in layer: pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, -0.5 * self.m1_width) - pwell_position = vector(0, pwell_min_offset) - pwell_height = middle_position.y - pwell_position.y + pwell_position = vector(-self.well_extend_active, pwell_min_offset) + pwell_height = nwell_y_offset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, width=self.well_width, @@ -181,7 +185,6 @@ class pgate(design.design): 0.5 * pmos.active_contact.first_layer_height) self.nwell_contact = self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="n", well_type="n") self.add_rect_center(layer="m1", @@ -235,7 +238,6 @@ class pgate(design.design): 0.5 * nmos.active_contact.first_layer_height) self.pwell_contact= self.add_via_center(layers=layer_stack, offset=contact_offset, - directions=("H", "V"), implant_type="p", well_type="p") self.add_rect_center(layer="m1", diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 8da648b0..c0f1f955 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -52,10 +52,10 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ self.setup_layout_constants() - self.route_supply_rails() self.place_ptx() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() + self.route_supply_rails() self.connect_rails() self.route_input_gate(self.pmos_inst, self.nmos_inst, @@ -104,8 +104,9 @@ class pinv(pgate.pgate): extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, - self.poly_extend_active, self.poly_space) + self.poly_extend_active + self.poly_space) total_height = tx_height + min_channel + 2 * self.top_bottom_space + debug.check(self.height > total_height, "Cell height {0} too small for simple min height {1}.".format(self.height, total_height)) @@ -148,14 +149,18 @@ class pinv(pgate.pgate): def setup_layout_constants(self): """ - Pre-compute some handy layout parameters. + Compute the width and height """ - # the well width is determined the multi-finger PMOS device width plus - # the well contact width and half well enclosure on both sides - self.well_width = self.pmos.active_width + self.pmos.active_contact.width \ - + self.active_space + 2*self.nwell_enclose_active - self.width = self.well_width + # the width is determined the multi-finger PMOS device width plus + # the well contact width, spacing between them + # space is for power supply contact to nwell m1 spacing + self.width = self.pmos.active_offset.x + self.pmos.active_width \ + + self.active_space + contact.nwell_contact.width \ + + 0.5 * self.nwell_enclose_active \ + + self.m1_space + # This includes full enclosures on each end + self.well_width = self.width + 2*self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. def add_ptx(self): @@ -223,9 +228,6 @@ class pinv(pgate.pgate): nmos_drain_pos = self.nmos_inst.get_pin("D").ul() self.output_pos = vector(0, 0.5 * (pmos_drain_pos.y + nmos_drain_pos.y)) - # This will help with the wells - self.well_pos = self.output_pos - def route_outputs(self): """ Route the output (drains) together. diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index f5abdaaa..dd04ccb9 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -53,7 +53,7 @@ class pnand2(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -98,11 +98,11 @@ class pnand2(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width + contact.active_contact.width \ + self.width = 2 * self.pmos.active_width + contact.active_contact.width \ + 2 * self.active_space \ - + 2 * self.nwell_enclose_active + + 0.5 * self.nwell_enclose_active - self.width = self.well_width + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. # This is the extra space needed to ensure DRC rules @@ -110,7 +110,7 @@ class pnand2(pgate.pgate): extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, - self.poly_extend_active, self.poly_space) + self.poly_extend_active + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ @@ -170,9 +170,6 @@ class pnand2(pgate.pgate): self.output_pos = vector(0, 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - # This will help with the wells - self.well_pos = self.output_pos - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies @@ -227,11 +224,11 @@ class pnand2(pgate.pgate): # Non-preferred active contacts self.add_via_center(layers=self.m1_stack, - directions=("V","H"), + directions=("V", "H"), offset=pmos_pin.center()) # Non-preferred active contacts self.add_via_center(layers=self.m1_stack, - directions=("V","H"), + directions=("V", "H"), offset=nmos_pin.center()) self.add_via_center(layers=self.m1_stack, offset=out_offset) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 5c1e0db5..9ec9ff60 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -61,7 +61,7 @@ class pnand3(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -91,10 +91,10 @@ class pnand3(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ - + 2 * self.active_space + 2 * self.nwell_enclose_active \ + self.width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ + + 2 * self.active_space + 0.5 * self.nwell_enclose_active \ - self.overlap_offset.x - self.width = self.well_width + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. # This is the extra space needed to ensure DRC rules @@ -102,10 +102,8 @@ class pnand3(pgate.pgate): nmos = factory.create(module_type="ptx", tx_type="nmos") extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space \ - + extra_contact_space, - self.poly_extend_active, - self.poly_space) + self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, + self.poly_extend_active + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ @@ -179,9 +177,6 @@ class pnand3(pgate.pgate): # This will help with the wells and the input/output placement self.output_pos = vector(0, 0.5*self.height) - # This should be placed at the top of the NMOS well - self.well_pos = self.output_pos - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -238,17 +233,20 @@ class pnand3(pgate.pgate): # Go up to metal2 for ease on all output pins self.add_via_center(layers=self.m1_stack, - offset=pmos1_pin.center()) + offset=pmos1_pin.center(), + directions=("V", "V")) self.add_via_center(layers=self.m1_stack, - offset=pmos3_pin.center()) + offset=pmos3_pin.center(), + directions=("V", "V")) self.add_via_center(layers=self.m1_stack, - offset=nmos3_pin.center()) + offset=nmos3_pin.center(), + directions=("V", "V")) # PMOS3 and NMOS3 are drain aligned - self.add_path("m2", [pmos3_pin.bc(), nmos3_pin.uc()]) + self.add_path("m2", [pmos3_pin.center(), nmos3_pin.uc()]) # Route in the A input track (top track) mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) - self.add_path("m2", [pmos1_pin.bc(), mid_offset, nmos3_pin.uc()]) + self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) # This extends the output to the edge of the cell self.add_via_center(layers=self.m1_stack, diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index e28b6fc8..d6605d68 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -52,7 +52,7 @@ class pnor2(pgate.pgate): self.place_ptx() self.connect_rails() self.add_well_contacts() - self.extend_wells(self.well_pos) + self.extend_wells() self.route_inputs() self.route_output() @@ -95,11 +95,11 @@ class pnor2(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width \ + self.width = 2 * self.pmos.active_width \ + self.pmos.active_contact.width \ + 2 * self.active_space \ - + 2 * self.nwell_enclose_active - self.width = self.well_width + + 0.5 * self.nwell_enclose_active + self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. # This is the extra space needed to ensure DRC rules @@ -168,9 +168,6 @@ class pnor2(pgate.pgate): self.output_pos = vector(0, 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) - # This will help with the wells - self.well_pos = self.output_pos - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -225,7 +222,7 @@ class pnor2(pgate.pgate): # PMOS1 to mid-drain to NMOS2 drain self.add_path("m2", - [pmos_pin.bc(), mid2_offset, mid3_offset]) + [pmos_pin.center(), mid2_offset, mid3_offset]) self.add_path("m2", [nmos_pin.rc(), mid1_offset, mid2_offset]) # This extends the output to the edge of the cell diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 888304ce..28b4d85a 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -8,7 +8,7 @@ import contact import design import debug -from tech import drc, parameter +from tech import parameter from vector import vector from globals import OPTS from sram_factory import factory diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 32bbeefe..f331dc28 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -154,17 +154,23 @@ class ptx(design.design): # Poly height must include poly extension over active self.poly_height = self.tx_width + 2 * self.poly_extend_active - - well_name = "{}well".format(self.well_type) # The active offset is due to the well extension - if well_name in layer: - well_enclose_active = drc(well_name + "_enclose_active") - self.active_offset = vector([well_enclose_active] * 2) + if "pwell" in layer: + pwell_enclose_active = drc("pwell_enclose_active") else: - self.active_offset = vector(0, 0) + pwell_enclose_active = 0 + if "nwell" in layer: + nwell_enclose_active = drc("nwell_enclose_active") + else: + nwell_enclose_active = 0 + # Use the max of either so that the poly gates will align properly + well_enclose_active = max(pwell_enclose_active, + nwell_enclose_active) + self.active_offset = vector([well_enclose_active] * 2) # Well enclosure of active, ensure minwidth as well + well_name = "{}well".format(self.well_type) if well_name in layer: well_width_rule = drc("minwidth_" + well_name) well_enclose_active = drc(well_name + "_enclose_active") @@ -179,7 +185,7 @@ class ptx(design.design): # The well is not included in the height and width self.height = self.poly_height self.width = self.active_width - + # This is the center of the first active contact offset (centered vertically) self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, 0.5 * self.active_height) @@ -334,13 +340,10 @@ class ptx(design.design): if not (well_name in layer or "vtg" in layer): return - center_pos = self.active_offset + vector(self.width / 2.0, - self.height / 2.0) - well_ll = center_pos - vector(self.well_width / 2.0, - self.well_height / 2.0) - well_ll = well_ll - vector(0, - self.poly_extend_active) - + center_pos = self.active_offset + vector(0.5*self.active_width, + 0.5*self.active_height) + well_ll = center_pos - vector(0.5*self.well_width, + 0.5*self.well_height) if well_name in layer: self.add_rect(layer=well_name, offset=well_ll, diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index ed970f9b..16830e62 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -6,14 +6,13 @@ #All rights reserved. # import design -from tech import drc, parameter, spice +from tech import parameter import debug -import math -from tech import drc from vector import vector from globals import OPTS from sram_factory import factory + class pwrite_driver(design.design): """ The pwrite_driver is two tristate inverters that drive the bitlines. diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 93247fc1..2f37d927 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -5,21 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys -from tech import drc, spice import debug -from math import log,sqrt,ceil -import datetime -import getpass -import numpy as np from vector import vector -from globals import OPTS, print_time - from sram_base import sram_base -from bank import bank from contact import m2_via -from dff_buf_array import dff_buf_array -from dff_array import dff_array class sram_1bank(sram_base): @@ -328,10 +317,10 @@ class sram_1bank(sram_base): offset=flop_pos) def route_col_addr_dff(self): - """ Connect the output of the row flops to the bank pins """ + """ Connect the output of the col flops to the bank pins """ for port in self.all_ports: if port%2: - offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.word_size+2)*self.m1_pitch) + offset = self.col_addr_dff_insts[port].ll() - vector(0, (self.col_addr_size+2)*self.m1_pitch) else: offset = self.col_addr_dff_insts[port].ul() + vector(0, 2*self.m1_pitch) diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py index f7a13118..d2f0c7f6 100755 --- a/compiler/tests/03_contact_test.py +++ b/compiler/tests/03_contact_test.py @@ -21,7 +21,7 @@ class contact_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - from tech import poly_stack, beol_stacks + from tech import active_stack, poly_stack, beol_stacks # Don't do active because of nwell contact rules # Don't do metal3 because of min area rules @@ -67,6 +67,25 @@ class contact_test(openram_test): c = factory.create(module_type="contact", layer_stack=layer_stack, dimensions=(3, 3)) self.local_drc_check(c) + # Test the well taps + # check vertical array with one in the middle and two ends + layer_stack = active_stack + stack_name = ":".join(map(str, layer_stack)) + + debug.info(2, "1 x 1 {} nwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="n", + well_type="n") + self.local_drc_check(c) + + debug.info(2, "1 x 1 {} pwell".format(stack_name)) + c = factory.create(module_type="contact", + layer_stack=layer_stack, + implant_type="p", + well_type="p") + self.local_drc_check(c) + globals.end_openram()