diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 1ffdc7bf..f4fda552 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -47,8 +47,16 @@ class contact(hierarchy_design.hierarchy_design): self.layer_stack = layer_stack self.dimensions = dimensions - if directions: + + # Non-preferred directions + if directions == "nonpref": + first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V" + second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V" + self.directions = (first_dir, second_dir) + # User directions + elif directions: self.directions = directions + # Preferred directions else: self.directions = (tech.preferred_directions[layer_stack[0]], tech.preferred_directions[layer_stack[2]]) diff --git a/compiler/base/design.py b/compiler/base/design.py index 73ace38e..7a570b28 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -6,6 +6,7 @@ # All rights reserved. # from hierarchy_design import hierarchy_design +from utils import round_to_grid import contact from globals import OPTS import re @@ -31,48 +32,98 @@ class design(hierarchy_design): in many places in the compiler. """ + from tech import layer_indices import tech - for key in dir(tech): - # Single layer width rules - match = re.match(r".*_stack$", key) - if match: + for layer in layer_indices: + key = "{}_stack".format(layer) + + # Set the stack as a local helper + try: layer_stack = getattr(tech, key) - - # Set the stack as a local helper setattr(self, key, layer_stack) + except AttributeError: + pass - # Add the pitch - setattr(self, - "{}_pitch".format(layer_stack[0]), - self.compute_pitch(layer_stack)) - + # Skip computing the pitch for active + if layer == "active": + continue + + # Add the pitch + setattr(self, + "{}_pitch".format(layer), + self.compute_pitch(layer, True)) + + # Add the non-preferrd pitch (which has vias in the "wrong" way) + setattr(self, + "{}_nonpref_pitch".format(layer), + self.compute_pitch(layer, False)) + if False: - print("m1_pitch", self.m1_pitch) - print("m2_pitch", self.m2_pitch) - print("m3_pitch", self.m3_pitch) + from tech import preferred_directions + print(preferred_directions) + from tech import layer, layer_indices + for name in layer_indices: + if name == "active": + continue + try: + print("{0} width {1} space {2}".format(name, + getattr(self, "{}_width".format(name)), + getattr(self, "{}_space".format(name)))) + + print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)), + getattr(self, "{}_nonpref_pitch".format(name)))) + except AttributeError: + pass import sys sys.exit(1) - def compute_pitch(self, layer_stack): + def compute_pitch(self, layer, preferred=True): """ - This is contact direction independent pitch, - i.e. we take the maximum contact dimension + This is the preferred direction pitch + i.e. we take the minimum or maximum contact dimension """ + # Find the layer stacks this is used in + from tech import layer_stacks + pitches = [] + for stack in layer_stacks: + # Compute the pitch with both vias above and below (if they exist) + if stack[0] == layer: + pitches.append(self.compute_layer_pitch(stack, preferred)) + if stack[2] == layer: + pitches.append(self.compute_layer_pitch(stack[::-1], True)) + + return max(pitches) + + def compute_layer_pitch(self, layer_stack, preferred): + (layer1, via, layer2) = layer_stack + try: + if layer1 == "poly" or layer1 == "active": + contact1 = getattr(contact, layer1 + "_contact") + else: + contact1 = getattr(contact, layer1 + "_via") + except AttributeError: + contact1 = getattr(contact, layer2 + "_via") - if layer1 == "poly" or layer1 == "active": - contact1 = getattr(contact, layer1 + "_contact") + if preferred: + if self.get_preferred_direction(layer1) == "V": + contact_width = contact1.first_layer_width + else: + contact_width = contact1.first_layer_height else: - contact1 = getattr(contact, layer1 + "_via") - max_contact = max(contact1.width, contact1.height) - - layer1_space = getattr(self, layer1 + "_space") - layer2_space = getattr(self, layer2 + "_space") - pitch = max_contact + max(layer1_space, layer2_space) + if self.get_preferred_direction(layer1) == "V": + contact_width = contact1.first_layer_height + else: + contact_width = contact1.first_layer_width + layer_space = getattr(self, layer1 + "_space") - return pitch - + #print(layer_stack) + #print(contact1) + pitch = contact_width + layer_space + + return round_to_grid(pitch) + def setup_drc_constants(self): """ These are some DRC constants used in many places diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index e6f9ac9f..df7095df 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -12,6 +12,7 @@ import debug from math import sqrt from tech import drc, GDS from tech import layer as techlayer +from tech import layer_indices from tech import layer_stacks import os from globals import OPTS @@ -41,12 +42,15 @@ class layout(): self.visited = [] # List of modules we have already visited self.is_library_cell = False # Flag for library cells self.gds_read() + try: from tech import power_grid self.pwr_grid_layer = power_grid[0] except ImportError: self.pwr_grid_layer = "m3" + + ############################################################ # GDS layout ############################################################ @@ -444,7 +448,40 @@ class layout(): path=coordinates, layer_widths=layer_widths) - def add_wire(self, layers, coordinates): + def add_zjog(self, layer, start, end, first_direction="H"): + """ + Add a simple jog at the halfway point. + If layer is a single value, it is a path. + If layer is a tuple, it is a wire with preferred directions. + """ + + # vertical first + if first_direction == "V": + mid1 = vector(start.x, 0.5 * start.y + 0.5 * end.y) + mid2 = vector(end.x, mid1.y) + # horizontal first + elif first_direction == "H": + mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) + mid2 = vector(mid1, end.y) + else: + debug.error("Invalid direction for jog -- must be H or V.") + + if layer in layer_stacks: + self.add_wire(layer, [start, mid1, mid2, end]) + elif layer in techlayer: + self.add_path(layer, [start, mid1, mid2, end]) + else: + debug.error("Could not find layer {}".format(layer)) + + def add_horizontal_zjog_path(self, layer, start, end): + """ Add a simple jog at the halfway point """ + + # horizontal first + mid1 = vector(0.5 * start.x + 0.5 * end.x, start.y) + mid2 = vector(mid1, end.y) + self.add_path(layer, [start, mid1, mid2, end]) + + def add_wire(self, layers, coordinates, widen_short_wires=True): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ import wire @@ -452,7 +489,8 @@ class layout(): # into rectangles and contacts wire.wire(obj=self, layer_stack=layers, - position_list=coordinates) + position_list=coordinates, + widen_short_wires=widen_short_wires) def get_preferred_direction(self, layer): """ Return the preferred routing directions """ @@ -461,11 +499,6 @@ class layout(): def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None): """ Add a three layer via structure. """ - - if not directions: - directions = (self.get_preferred_direction(layers[0]), - self.get_preferred_direction(layers[2])) - from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -486,11 +519,6 @@ class layout(): Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """ - - if not directions: - directions = (self.get_preferred_direction(layers[0]), - self.get_preferred_direction(layers[2])) - from sram_factory import factory via = factory.create(module_type="contact", layer_stack=layers, @@ -513,36 +541,47 @@ class layout(): return inst def add_via_stack(self, offset, from_layer, to_layer, - direction=None, - size=[1, 1]): + directions=None, + size=[1, 1], + implant_type=None, + well_type=None): """ Punch a stack of vias from a start layer to a target layer. """ return self.__add_via_stack_internal(offset=offset, - direction=direction, + directions=directions, from_layer=from_layer, to_layer=to_layer, via_func=self.add_via, last_via=None, - size=size) + size=size, + implant_type=implant_type, + well_type=well_type) - def add_via_stack_center(self, offset, from_layer, to_layer, - direction=None, - size=[1, 1]): + def add_via_stack_center(self, + offset, + from_layer, + to_layer, + directions=None, + size=[1, 1], + implant_type=None, + well_type=None): """ Punch a stack of vias from a start layer to a target layer by the center coordinate accounting for mirroring and rotation. """ return self.__add_via_stack_internal(offset=offset, - direction=direction, + directions=directions, from_layer=from_layer, to_layer=to_layer, via_func=self.add_via_center, last_via=None, - size=size) + size=size, + implant_type=implant_type, + well_type=well_type) - def __add_via_stack_internal(self, offset, direction, from_layer, to_layer, - via_func, last_via, size): + def __add_via_stack_internal(self, offset, directions, from_layer, to_layer, + via_func, last_via, size, implant_type=None, well_type=None): """ Punch a stack of vias from a start layer to a target layer. Here we figure out whether to punch it up or down the stack. @@ -551,8 +590,8 @@ class layout(): if from_layer == to_layer: return last_via - from_id = int(from_layer[1]) - to_id = int(to_layer[1]) + from_id = layer_indices[from_layer] + to_id = layer_indices[to_layer] if from_id < to_id: # grow the stack up search_id = 0 @@ -563,19 +602,25 @@ class layout(): curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None) if curr_stack is None: - raise ValueError("Cannot create via from '{0}' to '{1}'." \ - "Layer '{0}' not defined" - .format(from_layer, to_layer)) + raise ValueError("Cannot create via from '{0}' to '{1}'." + "Layer '{0}' not defined".format(from_layer, to_layer)) - via = via_func(layers=curr_stack, size=size, offset=offset, directions=direction) - return self.__add_via_stack_internal(offset=offset, - direction=direction, - from_layer=curr_stack[next_id], - to_layer=to_layer, - via_func=via_func, - last_via=via, - size=size) + via = via_func(layers=curr_stack, + size=size, + offset=offset, + directions=directions, + implant_type=implant_type, + well_type=well_type) + via = self.__add_via_stack_internal(offset=offset, + directions=directions, + from_layer=curr_stack[next_id], + to_layer=to_layer, + via_func=via_func, + last_via=via, + size=size) + return via + def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"): """Adds a ptx module to the design.""" import ptx @@ -729,47 +774,47 @@ class layout(): return blockages - def create_horizontal_pin_bus(self, layer, pitch, offset, names, length): + def create_horizontal_pin_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus of pins. """ return self.create_bus(layer, - pitch, offset, names, length, vertical=False, - make_pins=True) + make_pins=True, + pitch=pitch) - def create_vertical_pin_bus(self, layer, pitch, offset, names, length): + def create_vertical_pin_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus of pins. """ return self.create_bus(layer, - pitch, offset, names, length, vertical=True, - make_pins=True) + make_pins=True, + pitch=pitch) - def create_vertical_bus(self, layer, pitch, offset, names, length): + def create_vertical_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus. """ return self.create_bus(layer, - pitch, offset, names, length, vertical=True, - make_pins=False) + make_pins=False, + pitch=pitch) - def create_horizontal_bus(self, layer, pitch, offset, names, length): + def create_horizontal_bus(self, layer, offset, names, length, pitch=None): """ Create a horizontal bus. """ return self.create_bus(layer, - pitch, offset, names, length, vertical=False, - make_pins=False) + make_pins=False, + pitch=pitch) - def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins): + def create_bus(self, layer, offset, names, length, vertical, make_pins, pitch=None): """ Create a horizontal or vertical bus. It can be either just rectangles, or actual layout pins. It returns an map of line center line positions indexed by name. @@ -779,11 +824,14 @@ class layout(): # half minwidth so we can return the center line offsets half_minwidth = 0.5 * drc["minwidth_{}".format(layer)] + if not pitch: + pitch = getattr(self, "{}_pitch".format(layer)) line_positions = {} if vertical: for i in range(len(names)): - line_offset = offset + vector(i * pitch, 0) + line_offset = offset + vector(i * pitch, + 0) if make_pins: self.add_layout_pin(text=names[i], layer=layer, @@ -848,8 +896,10 @@ class layout(): # left/right then up/down mid_pos = vector(bus_pos.x, pin_pos.y) + # Don't widen short wires because pin_pos and mid_pos could be really close self.add_wire(layer_stack, - [bus_pos, mid_pos, pin_pos]) + [bus_pos, mid_pos, pin_pos], + widen_short_wires=False) # Connect to the pin on the instances with a via if it is # not on the right layer @@ -866,33 +916,76 @@ class layout(): offset=bus_pos, rotate=90) + def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"): + """ + Helper routine to connect an instance to a vertical bus. + Routes horizontal then vertical L shape. + """ + + if src_pin.cx() self.horiz_layer_contact_width: + if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width: width = self.horiz_layer_contact_width else: width = self.horiz_layer_width @@ -134,7 +137,7 @@ class wire(wire_path): line_length = pl[index + 1][1] - pl[index][1] # Make the wire wider to avoid via-to-via spacing problems # But don't make it wider if it is shorter than one via - if abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width: + if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width: width = self.vert_layer_contact_width else: width = self.vert_layer_width diff --git a/compiler/base/wire_path.py b/compiler/base/wire_path.py index ebfb8a0a..31d0ae78 100644 --- a/compiler/base/wire_path.py +++ b/compiler/base/wire_path.py @@ -24,11 +24,12 @@ def create_rectilinear_route(my_list): my_list.append(vector(pl[index][0], pl[index + 1][1])) my_list.append(vector(pl[-1])) return my_list - + + class wire_path(): """ Object metal wire_path; given the layer type - Add a wire_path of minimium metal width between a set of points. + Add a wire_path of minimium metal width between a set of points. The points should be rectilinear to control the bend points. If not, it will always go down first. The points are the center of the wire_path. If width is not given, it uses minimum layer width. @@ -37,7 +38,7 @@ class wire_path(): self.obj = obj self.layer_name = layer self.layer_id = techlayer[layer] - if width==None: + if width == None: self.layer_width = drc["minwidth_{0}".format(layer)] else: self.layer_width = width @@ -46,7 +47,6 @@ class wire_path(): self.switch_pos_list = [] self.create_layout() - def create_layout(self): self.create_rectilinear() self.connect_corner() @@ -60,9 +60,9 @@ class wire_path(): def connect_corner(self): """ Add a corner square at every corner of the wire_path.""" - from itertools import tee,islice - nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n)))) - threewise=nwise(self.position_list,3) + from itertools import tee, islice + nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n)))) + threewise=nwise(self.position_list, 3) for (a, offset, c) in list(threewise): # add a exceptions to prevent a corner when we retrace back in the same direction @@ -74,7 +74,6 @@ class wire_path(): offset[1] - 0.5 * self.layer_width] self.draw_corner_wire(corner_offset) - def draw_corner_wire(self, offset): """ This function adds the corner squares since the center line convention only draws to the center of the corner.""" @@ -117,7 +116,7 @@ class wire_path(): def add_line(self, layer_name, length, offset, orientation, layer_width): """ - straight line object with layer_minwidth + straight line object with layer_minwidth (orientation: "vertical" or "horizontal") default is vertical """ diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 68e70e7e..b0e79208 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -50,7 +50,7 @@ class bitcell(bitcell_base.bitcell_base): self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ if props.bitcell.split_wl: diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index be96a129..aea7a6dc 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -264,7 +264,7 @@ class pbitcell(bitcell_base.bitcell_base): + 2 * self.implant_enclose_active \ + 0.5*(self.inverter_pmos.active_contact.height - self.m1_width) metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space - self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width + self.vdd_offset = max(implant_constraint, metal1_constraint) + self.m1_width # read port dimensions width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx() @@ -275,7 +275,7 @@ class pbitcell(bitcell_base.bitcell_base): Calculate positions that describe the edges and dimensions of the cell """ - self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch + self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch self.topmost_ypos = self.inverter_nmos_ypos \ + self.inverter_nmos.active_height \ + self.inverter_gap \ @@ -378,14 +378,15 @@ class pbitcell(bitcell_base.bitcell_base): + 0.5 * contact.poly_contact.height, self.cross_couple_upper_ypos) self.add_via_center(layers=self.poly_stack, - offset=contact_offset_left) - + offset=contact_offset_left, + directions=("H", "H")) contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \ - 0.5*contact.poly_contact.height, self.cross_couple_lower_ypos) self.add_via_center(layers=self.poly_stack, - offset=contact_offset_right) + offset=contact_offset_right, + directions=("H", "H")) # connect contacts to gate poly (cross couple connections) gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, @@ -399,12 +400,12 @@ class pbitcell(bitcell_base.bitcell_base): def route_rails(self): """ Adds gnd and vdd rails and connects them to the inverters """ # Add rails for vdd and gnd - gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch + gnd_ypos = self.m1_offset - self.total_ports * self.m1_nonpref_pitch self.gnd_position = vector(0, gnd_ypos) self.add_rect_center(layer="m1", offset=self.gnd_position, width=self.width) - self.add_power_pin("gnd", vector(0, gnd_ypos)) + self.add_power_pin("gnd", vector(0, gnd_ypos), directions=("H", "H")) vdd_ypos = self.inverter_nmos_ypos \ @@ -416,7 +417,7 @@ class pbitcell(bitcell_base.bitcell_base): self.add_rect_center(layer="m1", offset=self.vdd_position, width=self.width) - self.add_power_pin("vdd", vector(0, vdd_ypos)) + self.add_power_pin("vdd", vector(0, vdd_ypos), directions=("H", "H")) def create_readwrite_ports(self): """ @@ -483,7 +484,7 @@ class pbitcell(bitcell_base.bitcell_base): self.port_ypos]) # add pin for RWWL - rwwl_ypos = self.m1_offset - k * self.m1_pitch + rwwl_ypos = self.m1_offset - k * self.m1_nonpref_pitch self.rwwl_positions[k] = vector(0, rwwl_ypos) self.add_layout_pin_rect_center(text=self.rw_wl_names[k], layer="m1", @@ -579,8 +580,8 @@ class pbitcell(bitcell_base.bitcell_base): # add pin for WWL wwl_ypos = rwwl_ypos = self.m1_offset \ - - self.num_rw_ports * self.m1_pitch \ - - k * self.m1_pitch + - self.num_rw_ports * self.m1_nonpref_pitch \ + - k * self.m1_nonpref_pitch self.wwl_positions[k] = vector(0, wwl_ypos) self.add_layout_pin_rect_center(text=self.w_wl_names[k], layer="m1", @@ -705,9 +706,9 @@ class pbitcell(bitcell_base.bitcell_base): # add pin for RWL rwl_ypos = rwwl_ypos = self.m1_offset \ - - self.num_rw_ports * self.m1_pitch \ - - self.num_w_ports * self.m1_pitch \ - - k * self.m1_pitch + - self.num_rw_ports * self.m1_nonpref_pitch \ + - self.num_w_ports * self.m1_nonpref_pitch \ + - k * self.m1_nonpref_pitch self.rwl_positions[k] = vector(0, rwl_ypos) self.add_layout_pin_rect_center(text=self.r_wl_names[k], layer="m1", @@ -771,6 +772,7 @@ class pbitcell(bitcell_base.bitcell_base): self.add_via_center(layers=self.poly_stack, offset=port_contact_offset) + self.add_path("poly", [gate_offset, port_contact_offset]) self.add_path("m1", [port_contact_offset, wl_contact_offset]) @@ -821,7 +823,8 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: self.add_via_center(layers=self.m1_stack, - offset=port_contact_offest) + offset=port_contact_offest, + directions="nonpref") self.add_path("m2", [port_contact_offest, bl_offset], width=contact.m1_via.height) @@ -833,7 +836,8 @@ class pbitcell(bitcell_base.bitcell_base): # Leave bitline disconnected if a dummy cell if not self.dummy_bitcell: self.add_via_center(layers=self.m1_stack, - offset=port_contact_offest) + offset=port_contact_offest, + directions="nonpref") self.add_path("m2", [port_contact_offest, br_offset], width=contact.m1_via.height) @@ -850,7 +854,9 @@ class pbitcell(bitcell_base.bitcell_base): for position in nmos_contact_positions: self.add_via_center(layers=self.m1_stack, - offset=position) + offset=position, + directions=("V", "V")) + if position.x > 0: contact_correct = 0.5 * contact.m1_via.height @@ -859,7 +865,8 @@ class pbitcell(bitcell_base.bitcell_base): supply_offset = vector(position.x + contact_correct, self.gnd_position.y) self.add_via_center(layers=self.m1_stack, - offset=supply_offset) + offset=supply_offset, + directions=("H", "H")) self.add_path("m2", [position, supply_offset]) @@ -924,13 +931,16 @@ class pbitcell(bitcell_base.bitcell_base): - self.poly_to_contact - 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) self.add_via_center(layers=self.poly_stack, - offset=left_storage_contact) + offset=left_storage_contact, + directions=("H", "H")) + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \ + self.poly_to_contact + 0.5*contact.poly_contact.width, self.cross_couple_upper_ypos) self.add_via_center(layers=self.poly_stack, - offset=right_storage_contact) + offset=right_storage_contact, + directions=("H", "H")) 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]) @@ -1007,8 +1017,7 @@ class pbitcell(bitcell_base.bitcell_base): well_height = self.vdd_position.y - inverter_well_ypos \ + self.nwell_enclose_active + drc["minwidth_tx"] - # FIXME fudge factor xpos - offset = [inverter_well_xpos + 2*self.nwell_enclose_active, inverter_well_ypos] + offset = [inverter_well_xpos, inverter_well_ypos] self.add_rect(layer="nwell", offset=offset, width=well_width, diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 597d2fc0..40eabde6 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -86,10 +86,10 @@ class bank(design.design): self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]), "OUTPUT") for port in self.write_ports: for bit in range(self.word_size + self.num_spare_cols): - self.add_pin("din{0}_{1}".format(port,bit), "INPUT") + self.add_pin("din{0}_{1}".format(port, bit), "INPUT") for port in self.all_ports: for bit in range(self.addr_size): - self.add_pin("addr{0}_{1}".format(port,bit), "INPUT") + self.add_pin("addr{0}_{1}".format(port, bit), "INPUT") # For more than one bank, we have a bank select and name # the signals gated_*. @@ -133,16 +133,14 @@ class bank(design.design): bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name) # This will ensure the pin is only on the top or bottom edge if port % 2: - via_offset = bl_pin.uc() + vector(0, self.m2_pitch) + via_offset = bl_pin.uc() + vector(0, 1.5 * self.m2_pitch) left_right_offset = vector(self.max_x_offset, via_offset.y) else: - via_offset = bl_pin.bc() - vector(0, self.m2_pitch) + via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch) left_right_offset = vector(self.min_x_offset, via_offset.y) - if bl_pin == "m1": - self.add_via_center(layers=self.m1_stack, - offset=via_offset) - self.add_via_center(layers=self.m2_stack, - offset=via_offset) + self.add_via_stack_center(from_layer=bl_pin.layer, + to_layer="m3", + offset=via_offset) self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), layer="m3", start=left_right_offset, @@ -643,11 +641,10 @@ class bank(design.design): # Port 0 # The bank is at (0,0), so this is to the left of the y-axis. # 2 pitches on the right for vias/jogs to access the inputs - control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset) + control_bus_offset = vector(-self.m3_pitch * self.num_control_lines[0] - self.m3_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2 * self.m1_pitch self.bus_xoffset[0] = self.create_bus(layer="m2", - pitch=self.m2_pitch, offset=control_bus_offset, names=self.control_signals[0], length=control_bus_length, @@ -658,11 +655,10 @@ class bank(design.design): if len(self.all_ports)==2: # The other control bus is routed up to two pitches above the bitcell array control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2 * self.m1_pitch - control_bus_offset = vector(self.bitcell_array_right + self.m2_pitch, + control_bus_offset = vector(self.bitcell_array_right + self.m3_pitch, self.max_y_offset - control_bus_length) # The bus for the right port is reversed so that the rbl_wl is closest to the array self.bus_xoffset[1] = self.create_bus(layer="m2", - pitch=self.m2_pitch, offset=control_bus_offset, names=list(reversed(self.control_signals[1])), length=control_bus_length, @@ -862,9 +858,9 @@ class bank(design.design): self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name) if port % 2: - offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_pitch, 0) + offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0) else: - offset = self.column_decoder_inst[port].lr() + vector(self.m2_pitch, 0) + offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0) decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names] @@ -872,7 +868,9 @@ class bank(design.design): column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names] route_map = list(zip(decode_pins, column_mux_pins)) - self.create_vertical_channel_route(route_map, offset, self.m1_stack) + self.create_vertical_channel_route(route_map, + offset, + self.m1_stack) def add_lvs_correspondence_points(self): """ @@ -928,30 +926,39 @@ class bank(design.design): # pre-decoder and this connection is in metal3 connection = [] connection.append((self.prefix + "p_en_bar{}".format(port), - self.port_data_inst[port].get_pin("p_en_bar").lc())) + self.port_data_inst[port].get_pin("p_en_bar").lc(), + self.port_data_inst[port].get_pin("p_en_bar").layer)) 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())) + self.bitcell_array_inst.get_pin(rbl_wl_name).lc(), + self.bitcell_array_inst.get_pin(rbl_wl_name).layer)) 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())) + self.port_data_inst[port].get_pin("w_en").rc(), + self.port_data_inst[port].get_pin("w_en").layer)) else: connection.append((self.prefix + "w_en{}".format(port), - self.port_data_inst[port].get_pin("w_en").lc())) + self.port_data_inst[port].get_pin("w_en").lc(), + self.port_data_inst[port].get_pin("w_en").layer)) if port in self.read_ports: connection.append((self.prefix + "s_en{}".format(port), - self.port_data_inst[port].get_pin("s_en").lc())) + self.port_data_inst[port].get_pin("s_en").lc(), + self.port_data_inst[port].get_pin("s_en").layer)) - for (control_signal, pin_pos) in connection: - control_mid_pos = self.bus_xoffset[port][control_signal] - control_pos = vector(self.bus_xoffset[port][control_signal].x, pin_pos.y) - self.add_wire(self.m1_stack, [control_mid_pos, control_pos, pin_pos]) - self.add_via_center(layers=self.m1_stack, - offset=control_pos) + for (control_signal, pin_pos, pin_layer) in connection: + if port==0: + y_offset = self.min_y_offset + else: + y_offset = self.max_y_offset + control_pos = vector(self.bus_xoffset[port][control_signal].x, y_offset) + if pin_layer == "m1": + self.add_wire(self.m1_stack, [control_pos, pin_pos]) + elif pin_layer == "m3": + self.add_wire(self.m2_stack[::-1], [control_pos, pin_pos]) # clk to wordline_driver control_signal = self.prefix + "wl_en{}".format(port) diff --git a/compiler/modules/bank_select.py b/compiler/modules/bank_select.py index 3be10d3e..b6246268 100644 --- a/compiler/modules/bank_select.py +++ b/compiler/modules/bank_select.py @@ -205,7 +205,7 @@ class bank_select(design.design): bank_sel_line_end = vector(xoffset_bank_sel, self.yoffset_maxpoint) self.add_path("m2", [bank_sel_line_pos, bank_sel_line_end]) self.add_via_center(layers=self.m1_stack, - offset=bank_sel_inv_pin.lc()) + offset=bank_sel_inv_pin.center()) # Route the pin to the left edge as well bank_sel_pin_pos=vector(0, 0) @@ -242,30 +242,31 @@ class bank_select(design.design): # Connect the logic output to inverter input out_pin = logic_inst.get_pin("Z") - out_pos = out_pin.rc() + out_pos = out_pin.center() in_pin = inv_inst.get_pin("A") - in_pos = in_pin.lc() + in_pos = in_pin.center() mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y) mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y) self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos]) # Connect the logic B input to bank_sel / bank_sel_bar - logic_pos = logic_inst.get_pin("B").lc() - vector(0.5 * contact.m1_via.height, 0) + logic_pin = logic_inst.get_pin("B") + logic_pos = logic_pin.center() input_pos = vector(xoffset_bank_signal, logic_pos.y) - self.add_path("m2", [logic_pos, input_pos]) - self.add_via_center(layers=self.m1_stack, - offset=logic_pos, - directions=("H", "H")) + self.add_path("m3", [logic_pos, input_pos]) + self.add_via_center(self.m2_stack, + input_pos) + self.add_via_stack_center(from_layer=logic_pin.layer, + to_layer="m3", + offset=logic_pos) # Connect the logic A input to the input pin - logic_pos = logic_inst.get_pin("A").lc() + logic_pin = logic_inst.get_pin("A") + logic_pos = logic_pin.center() input_pos = vector(0, logic_pos.y) - self.add_via_center(layers=self.m1_stack, - offset=logic_pos, - directions=("H", "H")) - self.add_via_center(layers=self.m2_stack, - offset=logic_pos, - directions=("H", "H")) + self.add_via_stack_center(from_layer=logic_pin.layer, + to_layer="m3", + offset=logic_pos) self.add_layout_pin_segment_center(text=input_name, layer="m3", start=input_pos, diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index 8ee9336b..a8829fc3 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -104,9 +104,9 @@ class bitcell_base_array(design.design): # For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space # Default uses prefered directions for each layer; this cell property is only currently used by s8 tech (03/20) try: - force_power_pins_vertical = cell_properties.bitcell_force_power_pins_vertical + bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions except AttributeError: - force_power_pins_vertical = None + bitcell_power_pin_directions = None # Add vdd/gnd via stacks for row in range(self.row_size): @@ -114,7 +114,10 @@ class bitcell_base_array(design.design): inst = self.cell_inst[row,col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(name=pin_name, loc=pin.center(), vertical=force_power_pins_vertical, start_layer=pin.layer) + self.add_power_pin(name=pin_name, + loc=pin.center(), + directions=bitcell_power_pin_directions, + start_layer=pin.layer) def _adjust_x_offset(self, xoffset, col, col_offset): tempx = xoffset diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 5dd8182e..078dc3ae 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -384,7 +384,10 @@ class control_logic(design.design): height = self.control_logic_center.y - self.m2_pitch offset = vector(self.ctrl_dff_array.width, 0) - self.rail_offsets = self.create_vertical_bus("m2", self.m2_pitch, offset, self.internal_bus_list, height) + self.rail_offsets = self.create_vertical_bus("m2", + offset, + self.internal_bus_list, + height) def create_instances(self): """ Create all the instances """ @@ -432,7 +435,7 @@ class control_logic(design.design): row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_rbl_delay_row(row) - row += 1 + row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 @@ -522,16 +525,8 @@ class control_logic(design.design): self.add_via_center(layers=self.m1_stack, offset=clk_pos) - # Connect this at the bottom of the buffer - out_pos = self.clk_buf_inst.get_pin("Z").center() - mid1 = vector(out_pos.x, 2 * self.m2_pitch) - mid2 = vector(self.rail_offsets["clk_buf"].x, mid1.y) - bus_pos = self.rail_offsets["clk_buf"] - self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=self.clk_buf_inst.get_pin("Z").center()) - + self.route_output_to_bus_jogged(self.clk_buf_inst, + "clk_buf") self.connect_output(self.clk_buf_inst, "Z", "clk_buf") def create_gated_clk_bar_row(self): @@ -541,7 +536,7 @@ class control_logic(design.design): self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar", mod=self.and2) - self.connect_inst(["cs", "clk_bar", "gated_clk_bar", "vdd", "gnd"]) + self.connect_inst(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) def place_gated_clk_bar_row(self, row): x_offset = self.control_x_offset @@ -554,31 +549,26 @@ class control_logic(design.design): def route_gated_clk_bar(self): clkbuf_map = zip(["A"], ["clk_buf"]) self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets) - + out_pos = self.clk_bar_inst.get_pin("Z").center() - in_pos = self.gated_clk_bar_inst.get_pin("B").center() - mid1 = vector(in_pos.x, out_pos.y) - self.add_path("m1", [out_pos, mid1, in_pos]) + in_pos = self.gated_clk_bar_inst.get_pin("A").center() + self.add_zjog("m1", out_pos, in_pos) # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["A"], ["cs"]) + clkbuf_map = zip(["B"], ["cs"]) self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, self.m2_stack[::-1]) # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=self.gated_clk_bar_inst.get_pin("A").center()) + b_pin = self.gated_clk_bar_inst.get_pin("B") + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m3", + offset=b_pin.center()) # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["Z"], ["gated_clk_bar"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_bar_inst, - self.rail_offsets, - self.m2_stack[::-1]) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=self.gated_clk_bar_inst.get_pin("Z").center()) + self.route_output_to_bus_jogged(self.gated_clk_bar_inst, + "gated_clk_bar") def create_gated_clk_buf_row(self): self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", @@ -594,7 +584,9 @@ class control_logic(design.design): def route_gated_clk_buf(self): clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.rail_offsets) clkbuf_map = zip(["Z"], ["gated_clk_buf"]) self.connect_vertical_bus(clkbuf_map, @@ -602,8 +594,10 @@ class control_logic(design.design): self.rail_offsets, self.m2_stack[::-1]) # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=self.gated_clk_buf_inst.get_pin("Z").center()) + z_pin = self.gated_clk_buf_inst.get_pin("Z") + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m2", + offset=z_pin.center()) def create_wlen_row(self): # input pre_p_en, output: wl_en @@ -647,11 +641,16 @@ class control_logic(design.design): in_map = zip(["A", "B"], ["gated_clk_buf", "rbl_bl_delay"]) self.connect_vertical_bus(in_map, self.p_en_bar_nand_inst, self.rail_offsets) - out_pos = self.p_en_bar_nand_inst.get_pin("Z").rc() - in_pos = self.p_en_bar_driver_inst.get_pin("A").lc() - mid1 = vector(out_pos.x, in_pos.y) - self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) - + out_pin = self.p_en_bar_nand_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.p_en_bar_driver_inst.get_pin("A") + in_pos = in_pin.center() + mid1 = vector(in_pos.x, out_pos.y) + self.add_path(out_pin.layer, [out_pos, mid1, in_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pin.center()) + self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar") def create_sen_row(self): @@ -704,11 +703,7 @@ class control_logic(design.design): # Connect from delay line # Connect to rail - rbl_map = zip(["Z"], ["rbl_bl_delay_bar"]) - self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets, ("m3", "via2", "m2")) - # The pin is on M1, so we need another via as well - self.add_via_center(layers=self.m1_stack, - offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) + self.route_output_to_bus_jogged(self.rbl_bl_delay_inv_inst, "rbl_bl_delay_bar") rbl_map = zip(["A"], ["rbl_bl_delay"]) self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) @@ -766,11 +761,12 @@ class control_logic(design.design): dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) else: dff_out_map = zip(["dout_bar_0"], ["cs"]) - self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("m3", "via2", "m2")) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, self.m2_stack[::-1]) # Connect the clock rail to the other clock rail + # by routing in the supply rail track to avoid channel conflicts in_pos = self.ctrl_dff_inst.get_pin("clk").uc() - mid_pos = in_pos + vector(0, 2 * self.m2_pitch) + mid_pos = in_pos + vector(0, self.and2.height) rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y) self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_via_center(layers=self.m1_stack, @@ -823,10 +819,10 @@ class control_logic(design.design): self.add_path("m1", [row_loc, pin_loc]) self.copy_layout_pin(self.delay_inst, "gnd") - self.copy_layout_pin(self.delay_inst, "vdd") + self.copy_layout_pin(self.delay_inst, "vdd") self.copy_layout_pin(self.ctrl_dff_inst, "gnd") - self.copy_layout_pin(self.ctrl_dff_inst, "vdd") + self.copy_layout_pin(self.ctrl_dff_inst, "vdd") def add_lvs_correspondence_points(self): """ This adds some points for easier debugging if LVS goes wrong. @@ -1000,3 +996,15 @@ class control_logic(design.design): offset = vector(x_offset, y_offset) inst.place(offset, mirror) return x_offset + inst.width + + def route_output_to_bus_jogged(self, inst, name): + # Connect this at the bottom of the buffer + out_pos = inst.get_pin("Z").center() + mid1 = vector(out_pos.x, out_pos.y - 0.25 * inst.mod.height) + mid2 = vector(self.rail_offsets[name].x, mid1.y) + bus_pos = self.rail_offsets[name] + self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) + # The pin is on M1, so we need another via as well + self.add_via_center(layers=self.m1_stack, + offset=out_pos) + diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index b34974e5..ad9bc1cc 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -62,7 +62,7 @@ class delay_chain(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): - self.inv = factory.create(module_type="pinv", route_output=False) + self.inv = factory.create(module_type="pinv") self.add_mod(self.inv) def create_inverters(self): diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 500767d8..0366b10b 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -180,7 +180,7 @@ class dff_buf(design.design): height=din_pin.height()) dout_pin = self.inv2_inst.get_pin("Z") - mid_pos = dout_pin.center() + vector(self.m1_pitch, 0) + mid_pos = dout_pin.center() + vector(self.m1_nonpref_pitch, 0) q_pos = mid_pos - vector(0, self.m2_pitch) self.add_layout_pin_rect_center(text="Q", layer="m2", diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 69822149..c1593932 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -12,7 +12,7 @@ from sram_factory import factory from vector import vector from globals import OPTS from errors import drc_error -from tech import cell_properties +from tech import cell_properties, layer class hierarchical_decoder(design.design): @@ -89,6 +89,7 @@ class hierarchical_decoder(design.design): self.place_pre_decoder() self.place_row_decoder() self.route_inputs() + self.route_outputs() self.route_decoder_bus() self.route_vdd_gnd() self.offset_all_coordinates() @@ -103,7 +104,7 @@ class hierarchical_decoder(design.design): height=self.cell_height) self.add_mod(self.and2) self.and3 = factory.create(module_type="pand3", - height=self.cell_height) + height=self.cell_height) self.add_mod(self.and3) self.add_decoders() @@ -186,19 +187,36 @@ class hierarchical_decoder(design.design): self.num_rows = math.ceil(self.num_outputs / self.cell_multiple) # We will place this many final decoders per row self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows) - + # We will need to use M2 and M3 in the vertical bus if we have multiple decoders per row + if self.decoders_per_row == 1: + self.decoder_bus_pitch = self.m2_pitch + elif self.decoders_per_row == 2: + self.decoder_bus_pitch = self.m3_pitch + else: + debug.error("Insufficient layers for multi-bit height decoder.", -1) + # Calculates height and width of row-decoder if (self.num_inputs == 4 or self.num_inputs == 5): nand_width = self.and2.width + nand_inputs = 2 else: nand_width = self.and3.width - self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1) + nand_inputs = 3 + self.internal_routing_width = self.decoder_bus_pitch * (self.total_number_of_predecoder_outputs + 1) self.row_decoder_height = self.inv.height * self.num_rows + + decoder_input_wire_height = self.decoders_per_row * nand_inputs * self.m2_pitch + # print(self.decoders_per_row, nand_inputs) + # print(decoder_input_wire_height, self.cell_height) + if decoder_input_wire_height > self.cell_height: + debug.warning("Cannot fit multi-bit decoder routes per row.") + # debug.check(decoder_input_wire_height < self.cell_height, "Cannot fit multi-bit decoder routes per row.") self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch + # Calculates height and width of hierarchical decoder # Add extra pitch for good measure - self.height = max(self.predecoder_height, self.row_decoder_height) + self.m3_pitch + self.height = max(self.predecoder_height, self.row_decoder_height) + self.m2_pitch self.width = self.input_routing_width + self.predecoder_width \ + self.internal_routing_width \ + self.decoders_per_row * nand_width + self.inv.width @@ -218,7 +236,6 @@ class hierarchical_decoder(design.design): input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] self.input_bus = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, offset=input_offset, names=input_bus_names, length=input_height) @@ -238,7 +255,7 @@ class hierarchical_decoder(design.design): # To prevent conflicts, we will offset each input connect so # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) + decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch)) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -254,7 +271,7 @@ class hierarchical_decoder(design.design): # To prevent conflicts, we will offset each input connect so # that it aligns with the vdd/gnd rails - decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) + decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * (self.inv.height + self.m1_pitch)) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -265,11 +282,13 @@ class hierarchical_decoder(design.design): vertical M2 coordinate to the predecode inputs """ - self.add_via_center(layers=self.m2_stack, - offset=input_offset) - self.add_via_center(layers=self.m2_stack, - offset=output_offset) - self.add_path(("m3"), [input_offset, output_offset]) + self.add_via_stack_center(from_layer="m2", + to_layer="m3", + offset=input_offset) + self.add_via_stack_center(from_layer="m2", + to_layer="m3", + offset=output_offset) + self.add_path("m3", [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -411,7 +430,7 @@ class hierarchical_decoder(design.design): """ if (self.num_inputs >= 4): self.place_decoder_and_array() - self.route_decoder() + def place_decoder_and_array(self): """ @@ -447,16 +466,28 @@ class hierarchical_decoder(design.design): self.and_inst[inst_index].place(offset=vector(x_off, y_off), mirror=mirror) - def route_decoder(self): + def route_outputs(self): """ Add the pins. """ - for output in range(self.num_outputs): - z_pin = self.and_inst[output].get_pin("Z") - self.add_layout_pin(text="decode_{0}".format(output), - layer="m1", - offset=z_pin.ll(), - width=z_pin.width(), - height=z_pin.height()) + max_xoffset = max(x.rx() for x in self.and_inst) + + for output_index in range(self.num_outputs): + row_remainder = (output_index % self.decoders_per_row) + + and_inst = self.and_inst[output_index] + z_pin = and_inst.get_pin("Z") + if row_remainder == 0 and self.decoders_per_row > 1: + layer = "m3" + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m3", + offset=z_pin.center()) + else: + layer = z_pin.layer + + self.add_layout_pin_segment_center(text="decode_{0}".format(output_index), + layer=layer, + start=z_pin.center(), + end=vector(max_xoffset, z_pin.cy())) def route_decoder_bus(self): """ @@ -468,7 +499,7 @@ class hierarchical_decoder(design.design): # This leaves an offset for the predecoder output jogs input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)] self.predecode_bus = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, + pitch=self.decoder_bus_pitch, offset=vector(0, 0), names=input_bus_names, length=self.height) @@ -512,22 +543,27 @@ class hierarchical_decoder(design.design): """ output_index = 0 + if "li" in layer: + self.decoder_layers = [self.m1_stack, self.m2_stack[::-1]] + else: + self.decoder_layers = [self.m2_stack[::-1]] + debug.check(self.decoders_per_row <= len(self.decoder_layers), "Must have more layers for multi-height decoder.") + if (self.num_inputs == 4 or self.num_inputs == 5): for index_B in self.predec_groups[1]: for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? if (output_index < self.num_outputs): - row_index = math.floor(output_index / self.decoders_per_row) - row_remainder = (output_index % self.decoders_per_row) - row_offset = row_index * self.and_inst[0].height + (2 * row_remainder + 1) * self.m3_pitch predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - row_offset) + output_index, + 0) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - row_offset + self.m3_pitch) + output_index, + 1) output_index = output_index + 1 elif (self.num_inputs > 5): @@ -536,21 +572,21 @@ class hierarchical_decoder(design.design): for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? if (output_index < self.num_outputs): - row_index = math.floor(output_index / self.decoders_per_row) - row_remainder = (output_index % self.decoders_per_row) - row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m3_pitch predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - row_offset) + output_index, + 0) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - row_offset + self.m3_pitch) + output_index, + 1) predecode_name = "predecode_{}".format(index_C) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("C"), - row_offset + 2 * self.m3_pitch) + output_index, + 2) output_index = output_index + 1 def route_vdd_gnd(self): @@ -570,39 +606,56 @@ class hierarchical_decoder(design.design): # The nand and inv are the same height rows... supply_pin = self.and_inst[num].get_pin(pin_name) pin_pos = vector(xoffset, supply_pin.cy()) - self.add_path("m1", + self.add_path(supply_pin.layer, [supply_pin.lc(), vector(xoffset, supply_pin.cy())]) self.add_power_pin(name=pin_name, - loc=pin_pos) + loc=pin_pos, + start_layer=supply_pin.layer) # Copy the pins from the predecoders for pre in self.pre2x4_inst + self.pre3x8_inst: self.copy_layout_pin(pre, "vdd") self.copy_layout_pin(pre, "gnd") - def route_predecode_bus_outputs(self, rail_name, pin, y_offset): + def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index): """ Connect the routing rail to the given metal1 pin using a routing track at the given y_offset - """ - pin_pos = pin.center() - # If we have a single decoder per row, we can route on M1 - if self.decoders_per_row == 1: - rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) - self.add_path("m1", [rail_pos, pin_pos]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) - # If not, we must route over the decoder cells on M3 - else: - rail_pos = vector(self.predecode_bus[rail_name].x, y_offset) - mid_pos = vector(pin_pos.x, rail_pos.y) - self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos]) - self.add_via_center(layers=self.m2_stack, - offset=rail_pos) - self.add_via_center(layers=self.m1_stack, - offset=pin_pos) + row_index = math.floor(output_index / self.decoders_per_row) + row_remainder = (output_index % self.decoders_per_row) + row_offset = row_index * self.and_inst[0].height + + pin_pos = pin.center() + + # y_offset is the same for both the M2 and M4 routes so that the rail + # contacts align and don't cause problems + if pin_index == 0: + # Bottom pitch + y_offset = row_offset + elif pin_index == 1: + # One pitch from top + y_offset = row_offset + self.and_inst[0].height - self.m3_pitch + elif pin_index == 2: + # One pitch from bottom + y_offset = row_offset + self.m3_pitch + else: + debug.error("Invalid decoder pitch.") + + rail_pos = vector(self.predecode_bus[rail_name].x, y_offset) + mid_pos = vector(pin_pos.x, rail_pos.y) + self.add_wire(self.decoder_layers[row_remainder], [rail_pos, mid_pos, pin_pos]) + + self.add_via_stack_center(from_layer="m2", + to_layer=self.decoder_layers[row_remainder][0], + offset=rail_pos) + + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.decoder_layers[row_remainder][2], + offset=pin_pos, + directions=("H", "H")) + def route_predecode_bus_inputs(self, rail_name, pin, x_offset): """ Connect the routing rail to the given metal1 pin using a jog @@ -610,13 +663,21 @@ class hierarchical_decoder(design.design): """ # This routes the pin up to the rail, basically, to avoid conflicts. # It would be fixed with a channel router. + # pin_pos = pin.center() + # mid_point1 = vector(x_offset, pin_pos.y) + # mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2) + # rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) + # self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos]) + pin_pos = pin.center() - mid_point1 = vector(x_offset, pin_pos.y) - mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2) - rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) - self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) + rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) + self.add_path("m1", [pin_pos, rail_pos]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m1", + offset=pin_pos) + self.add_via_stack_center(from_layer="m1", + to_layer="m2", + offset=rail_pos) def input_load(self): if self.determine_predecodes(self.num_inputs)[1]==0: diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index ccee34a9..9a658546 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -11,6 +11,7 @@ import math import contact from vector import vector from sram_factory import factory +from tech import cell_properties class hierarchical_predecode(design.design): @@ -19,7 +20,17 @@ class hierarchical_predecode(design.design): """ def __init__(self, name, input_number, height=None): self.number_of_inputs = input_number - self.cell_height = height + + if not height: + b = factory.create(module_type="bitcell") + try: + self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple + except AttributeError: + self.cell_multiple = 1 + self.cell_height = self.cell_multiple * b.height + else: + self.cell_height = height + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -57,10 +68,10 @@ class hierarchical_predecode(design.design): self.height = self.number_of_outputs * self.and_mod.height # x offset for input inverters - self.x_off_inv_1 = self.number_of_inputs*self.m2_pitch + self.x_off_inv_1 = self.number_of_inputs * self.m2_pitch + self.m2_space # x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra m2 pitches - self.x_off_and = self.x_off_inv_1 + self.inv.width + (2*self.number_of_inputs + 2) * self.m2_pitch + self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.m2_pitch # x offset to output inverters self.width = self.x_off_and + self.and_mod.width @@ -68,9 +79,8 @@ class hierarchical_predecode(design.design): def route_rails(self): """ Create all of the rails for the inputs and vdd/gnd/inputs_bar/inputs """ input_names = ["in_{}".format(x) for x in range(self.number_of_inputs)] - offset = vector(0.5 * self.m2_width, self.m1_pitch) + offset = vector(0.5 * self.m2_width, self.m3_pitch) self.input_rails = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, offset=offset, names=input_names, length=self.height - 2 * self.m1_pitch) @@ -78,9 +88,8 @@ class hierarchical_predecode(design.design): invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] decode_names = invert_names + non_invert_names - offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m1_pitch) + offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m2_pitch, self.m3_pitch) self.decode_rails = self.create_vertical_bus(layer="m2", - pitch=self.m2_pitch, offset=offset, names=decode_names, length=self.height - 2 * self.m1_pitch) @@ -146,16 +155,18 @@ class hierarchical_predecode(design.design): # route one signal next to each vdd/gnd rail since this is # typically where the p/n devices are and there are no # pins in the and gates. - y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m1_via.width + self.m1_space + y_offset = (num + self.number_of_inputs) * self.inv.height + contact.m2_via.width + self.m2_space in_pin = "in_{}".format(num) a_pin = "A_{}".format(num) in_pos = vector(self.input_rails[in_pin].x, y_offset) a_pos = vector(self.decode_rails[a_pin].x, y_offset) self.add_path("m1", [in_pos, a_pos]) - self.add_via_center(layers=self.m1_stack, - offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers=self.m1_stack, - offset=[self.decode_rails[a_pin].x, y_offset]) + self.add_via_stack_center(from_layer="m1", + to_layer="m2", + offset=[self.input_rails[in_pin].x, y_offset]) + self.add_via_stack_center(from_layer="m1", + to_layer="m2", + offset=[self.decode_rails[a_pin].x, y_offset]) def route_output_and(self): """ @@ -165,37 +176,43 @@ class hierarchical_predecode(design.design): z_pin = self.and_inst[num].get_pin("Z") self.add_layout_pin(text="out_{}".format(num), - layer="m1", + layer=z_pin.layer, offset=z_pin.ll(), height=z_pin.height(), width=z_pin.width()) def route_input_inverters(self): """ - Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] + Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] """ for inv_num in range(self.number_of_inputs): out_pin = "Abar_{}".format(inv_num) in_pin = "in_{}".format(inv_num) - #add output so that it is just below the vdd or gnd rail + # add output so that it is just below the vdd or gnd rail # since this is where the p/n devices are and there are no # pins in the and gates. - y_offset = (inv_num+1) * self.inv.height - 3*self.m1_space - inv_out_pos = self.in_inst[inv_num].get_pin("Z").rc() - right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(),0) - rail_pos = vector(self.decode_rails[out_pin].x,y_offset) - self.add_path("m1", [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) - self.add_via_center(layers = self.m1_stack, - offset=rail_pos) - + y_offset = (inv_num + 1) * self.inv.height - 3 * self.m1_space + inv_out_pin = self.in_inst[inv_num].get_pin("Z") + inv_out_pos = inv_out_pin.rc() + right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").lx(), 0) + rail_pos = vector(self.decode_rails[out_pin].x, y_offset) + self.add_path(inv_out_pin.layer, [inv_out_pos, right_pos, vector(right_pos.x, y_offset), rail_pos]) + self.add_via_stack_center(from_layer=inv_out_pin.layer, + to_layer="m2", + offset=rail_pos) - #route input - inv_in_pos = self.in_inst[inv_num].get_pin("A").lc() - in_pos = vector(self.input_rails[in_pin].x,inv_in_pos.y) + # route input + pin = self.in_inst[inv_num].get_pin("A") + inv_in_pos = pin.lc() + in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y) self.add_path("m1", [in_pos, inv_in_pos]) - self.add_via_center(layers=self.m1_stack, - offset=in_pos) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m1", + offset=inv_in_pos) + self.add_via_stack_center(from_layer="m1", + to_layer="m2", + offset=in_pos) def route_and_to_rails(self): # This 2D array defines the connection mapping @@ -205,17 +222,28 @@ class hierarchical_predecode(design.design): index_lst= and_input_line_combination[k] if self.number_of_inputs == 2: - gate_lst = ["A","B"] + gate_lst = ["A", "B"] else: - gate_lst = ["A","B","C"] + gate_lst = ["A", "B", "C"] # this will connect pins A,B or A,B,C - for rail_pin,gate_pin in zip(index_lst,gate_lst): - pin_pos = self.and_inst[k].get_pin(gate_pin).lc() + for rail_pin, gate_pin in zip(index_lst, gate_lst): + pin = self.and_inst[k].get_pin(gate_pin) + pin_pos = pin.center() rail_pos = vector(self.decode_rails[rail_pin].x, pin_pos.y) self.add_path("m1", [rail_pos, pin_pos]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) + self.add_via_stack_center(from_layer="m1", + to_layer="m2", + offset=rail_pos) + if gate_pin == "A": + direction = None + else: + direction = ("H", "H") + + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m1", + offset=pin_pos, + directions=direction) def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ @@ -230,14 +258,16 @@ class hierarchical_predecode(design.design): for n in ["vdd", "gnd"]: and_pin = self.and_inst[num].get_pin(n) supply_offset = and_pin.ll().scale(0, 1) - self.add_rect(layer="m1", + self.add_rect(layer=and_pin.layer, offset=supply_offset, width=self.and_inst[num].rx()) # Add pins in two locations for xoffset in [in_xoffset]: pin_pos = vector(xoffset, and_pin.cy()) - self.add_power_pin(n, pin_pos) + self.add_power_pin(name=n, + loc=pin_pos, + start_layer=and_pin.layer) diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index bc204370..f9befc3f 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -3,8 +3,6 @@ # Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # -import sys -from tech import drc, parameter from math import log, ceil import debug import design @@ -13,6 +11,7 @@ from vector import vector from globals import OPTS + class port_address(design.design): """ Create the address port (row decoder and wordline driver).. @@ -25,17 +24,16 @@ class port_address(design.design): self.addr_size = ceil(log(self.num_rows, 2)) if name == "": - name = "port_address_{0}_{1}".format(cols,rows) + name = "port_address_{0}_{1}".format(cols, rows) design.design.__init__(self, name) - debug.info(2, "create data port of cols {0} rows {1}".format(cols,rows)) + debug.info(2, "create data port of cols {0} rows {1}".format(cols, rows)) self.create_netlist() if not OPTS.netlist_only: - debug.check(len(self.all_ports)<=2,"Bank layout cannot handle more than two ports.") + debug.check(len(self.all_ports) <= 2, "Bank layout cannot handle more than two ports.") self.create_layout() self.add_boundary() - def create_netlist(self): self.add_pins() self.add_modules() @@ -51,16 +49,15 @@ class port_address(design.design): """ Adding pins for port address module""" for bit in range(self.addr_size): - self.add_pin("addr_{0}".format(bit),"INPUT") + self.add_pin("addr_{0}".format(bit), "INPUT") self.add_pin("wl_en", "INPUT") for bit in range(self.num_rows): - self.add_pin("wl_{0}".format(bit),"OUTPUT") + self.add_pin("wl_{0}".format(bit), "OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") - + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def route_layout(self): """ Create routing amoung the modules """ @@ -71,8 +68,8 @@ class port_address(design.design): def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ for inst in self.insts: - self.copy_power_pins(inst,"vdd") - self.copy_power_pins(inst,"gnd") + self.copy_power_pins(inst, "vdd") + self.copy_power_pins(inst, "gnd") def route_pins(self): for row in range(self.addr_size): @@ -119,12 +116,10 @@ class port_address(design.design): temp.extend(["vdd", "gnd"]) self.connect_inst(temp) - - def create_wordline_driver(self): """ Create the Wordline Driver """ - self.wordline_driver_inst = self.add_inst(name="wordline_driver", + self.wordline_driver_inst = self.add_inst(name="wordline_driver", mod=self.wordline_driver) temp = [] @@ -137,19 +132,13 @@ class port_address(design.design): temp.append("gnd") self.connect_inst(temp) - - def place_instances(self): """ Compute the offsets and place the instances. """ - # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), - 3*self.m2_pitch) - - row_decoder_offset = vector(0,0) - wordline_driver_offset = vector(self.row_decoder.width + self.m2_gap,0) + row_decoder_offset = vector(0, 0) + wordline_driver_offset = vector(self.row_decoder.width, 0) self.wordline_driver_inst.place(wordline_driver_offset) self.row_decoder_inst.place(row_decoder_offset) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 1d434929..9ff99bdd 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -789,7 +789,7 @@ class port_data(design.design): # Channel route each mux separately since we don't minimize the number # of tracks in teh channel router yet. If we did, we could route all the bits at once! - offset = bot_inst_group.inst.ul() + vector(0, self.m1_pitch) + offset = bot_inst_group.inst.ul() + vector(0, self.m1_nonpref_pitch) for bit in range(num_bits): bottom_names = self._get_bitline_pins(bot_inst_group, bit) top_names = self._get_bitline_pins(top_inst_group, bit) diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index e405401a..349655c4 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -355,32 +355,30 @@ class replica_bitcell_array(design.design): width=self.width, height=pin.height()) - # Replica bitlines - for port in range(self.left_rbl+self.right_rbl): + for port in range(self.left_rbl + self.right_rbl): inst = self.replica_col_inst[port] - for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]): + for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(), self.replica_bl_names[port]): pin = inst.get_pin(pin_name) if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names: name = bl_name else: - name = "rbl_{0}_{1}".format(pin_name,port) + name = "rbl_{0}_{1}".format(pin_name, port) self.add_layout_pin(text=name, layer=pin.layer, - offset=pin.ll().scale(1,0), + offset=pin.ll().scale(1, 0), width=pin.width(), height=self.height) - - for pin_name in ["vdd","gnd"]: + for pin_name in ["vdd", "gnd"]: for inst in self.insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: - self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer) + self.add_power_pin(name=pin_name, + loc=pin.center(), + directions=("V", "V"), + start_layer=pin.layer) - - - def get_rbl_wl_name(self, port): """ Return the WL for the given RBL port """ return self.rbl_wl_names[port] @@ -399,7 +397,7 @@ class replica_bitcell_array(design.design): # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() - cell_load = 2 * bl_wire.return_input_cap() + cell_load = 2 * bl_wire.return_input_cap() bl_swing = OPTS.rbl_delay_percentage freq = spice["default_event_frequency"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 837bf4b4..db6dfe22 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -151,13 +151,13 @@ class sense_amp_array(design.design): self.add_power_pin(name="gnd", loc=gnd_pin.center(), start_layer=gnd_pin.layer, - vertical=True) + directions=("V", "V")) vdd_pin = inst.get_pin("vdd") self.add_power_pin(name="vdd", loc=vdd_pin.center(), start_layer=vdd_pin.layer, - vertical=True) + directions=("V", "V")) bl_pin = inst.get_pin(inst.mod.get_bl_names()) br_pin = inst.get_pin(inst.mod.get_br_names()) diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py index 76a31074..55f5e707 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver.py @@ -8,7 +8,7 @@ import debug import design import math -import contact +from tech import drc from vector import vector from sram_factory import factory from globals import OPTS @@ -26,8 +26,8 @@ class wordline_driver(design.design): debug.info(1, "Creating {0}".format(self.name)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - self.rows = rows - self.cols = cols + self.bitcell_rows = rows + self.bitcell_cols = cols b = factory.create(module_type="bitcell") try: @@ -36,6 +36,11 @@ class wordline_driver(design.design): self.cell_multiple = 1 self.cell_height = self.cell_multiple * b.height + # We may have more than one bitcell per decoder row + self.num_rows = math.ceil(self.bitcell_rows / self.cell_multiple) + # We will place this many final decoders per row + self.decoders_per_row = math.ceil(self.bitcell_rows / self.num_rows) + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -56,10 +61,10 @@ class wordline_driver(design.design): def add_pins(self): # inputs to wordline_driver. - for i in range(self.rows): + for i in range(self.bitcell_rows): self.add_pin("in_{0}".format(i), "INPUT") # Outputs from wordline_driver. - for i in range(self.rows): + for i in range(self.bitcell_rows): self.add_pin("wl_{0}".format(i), "OUTPUT") self.add_pin("en", "INPUT") self.add_pin("vdd", "POWER") @@ -68,7 +73,7 @@ class wordline_driver(design.design): def add_modules(self): self.and2 = factory.create(module_type="pand2", height=self.cell_height, - size=self.cols) + size=self.bitcell_cols) self.add_mod(self.and2) def route_vdd_gnd(self): @@ -79,7 +84,7 @@ class wordline_driver(design.design): # Find the x offsets for where the vias/pins should be placed xoffset_list = [self.and_inst[0].lx()] - for num in range(self.rows): + for num in range(self.bitcell_rows): # this will result in duplicate polygons for rails, but who cares # use the inverter offset even though it will be the and's too @@ -97,32 +102,34 @@ class wordline_driver(design.design): def create_drivers(self): self.and_inst = [] - for row in range(self.rows): + for row in range(self.bitcell_rows): name_and = "wl_driver_and{}".format(row) # add and2 self.and_inst.append(self.add_inst(name=name_and, mod=self.and2)) - self.connect_inst(["en", - "in_{0}".format(row), + self.connect_inst(["in_{0}".format(row), + "en", "wl_{0}".format(row), "vdd", "gnd"]) def setup_layout_constants(self): # We may have more than one bitcell per decoder row - self.num_rows = math.ceil(self.rows / self.cell_multiple) + self.driver_rows = math.ceil(self.bitcell_rows / self.cell_multiple) # We will place this many final decoders per row - self.decoders_per_row = math.ceil(self.rows / self.num_rows) + self.decoders_per_row = math.ceil(self.bitcell_rows / self.driver_rows) def place_drivers(self): - and2_xoffset = 2 * self.m1_width + 5 * self.m1_space + + # Leave a well gap to separate the bitcell array well from this well + well_gap = 2 * drc("pwell_to_nwell") + drc("nwell_enclose_active") - self.width = and2_xoffset + self.and2.width - self.height = self.and2.height * self.num_rows + self.width = self.decoders_per_row * self.and2.width + well_gap + self.height = self.and2.height * self.driver_rows - for row in range(self.rows): - #row = math.floor(inst_index / self.decoders_per_row) - #dec = inst_index % self.decoders_per_row + for inst_index in range(self.bitcell_rows): + row = math.floor(inst_index / self.decoders_per_row) + dec = inst_index % self.decoders_per_row if (row % 2): y_offset = self.and2.height * (row + 1) @@ -131,63 +138,42 @@ class wordline_driver(design.design): y_offset = self.and2.height * row inst_mirror = "R0" - # x_off = self.internal_routing_width + dec * and_mod.width - and2_offset = [and2_xoffset, y_offset] + x_offset = dec * self.and2.width + and2_offset = [x_offset, y_offset] # add and2 - self.and_inst[row].place(offset=and2_offset, - mirror=inst_mirror) + self.and_inst[inst_index].place(offset=and2_offset, + mirror=inst_mirror) def route_layout(self): """ Route all of the signals """ # Wordline enable connection - en_offset = [self.m1_width + 2 * self.m1_space, 0] + en_pin = self.and_inst[0].get_pin("B") + en_bottom_pos = vector(en_pin.lx(), 0) en_pin = self.add_layout_pin(text="en", layer="m2", - offset=en_offset, - width=self.m2_width, + offset=en_bottom_pos, height=self.height) - for row in range(self.rows): - and_inst = self.and_inst[row] - - # en connection - a_pin = and_inst.get_pin("A") - a_pos = a_pin.lc() - clk_offset = vector(en_pin.bc().x, a_pos.y) - self.add_segment_center(layer="m1", - start=clk_offset, - end=a_pos) - self.add_via_center(layers=self.m1_stack, - offset=clk_offset) + for inst_index in range(self.bitcell_rows): + and_inst = self.and_inst[inst_index] + row = math.floor(inst_index / self.decoders_per_row) - # connect the decoder input pin to and2 B + # Drop a via b_pin = and_inst.get_pin("B") - b_pos = b_pin.lc() - # needs to move down since B and input is - # nearly aligned with A inv input - up_or_down = self.m2_space if row % 2 else -self.m2_space - input_offset = vector(0, b_pos.y + up_or_down) - base_offset = vector(clk_offset.x, input_offset.y) - contact_offset = vector(0.5 * self.m2_width + self.m2_space + 0.5 * contact.m1_via.width, 0) - mid_via_offset = base_offset + contact_offset - + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m2", + offset=b_pin.center()) + + # connect the decoder input pin to and2 A + a_pin = and_inst.get_pin("A") + a_pos = a_pin.center() # must under the clk line in M1 self.add_layout_pin_segment_center(text="in_{0}".format(row), layer="m1", - start=input_offset, - end=mid_via_offset) - self.add_via_center(layers=self.m1_stack, - offset=mid_via_offset, - directions=("V", "V")) - - # now connect to the and2 B - self.add_path("m2", [mid_via_offset, b_pos]) - contact_offset = b_pos - vector(0.5 * contact.m1_via.height, 0) - self.add_via_center(layers=self.m1_stack, - offset=contact_offset, - directions=("H", "H")) + start=vector(0, a_pos.y), + end=a_pos) # output each WL on the right wl_offset = and_inst.get_pin("Z").rc() diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index a7ec61f0..640db97e 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -209,7 +209,7 @@ class write_driver_array(design.design): for pin in pin_list: self.add_power_pin(name=n, loc=pin.center(), - vertical=True, + directions=("V", "V"), start_layer=pin.layer) if self.write_size: for bit in range(self.num_wmasks): diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 25acb324..4d4e0c50 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -5,9 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -from tech import drc import debug from sram_factory import factory from vector import vector @@ -43,33 +41,28 @@ class write_mask_and_array(design.design): self.add_pins() self.create_and2_array() - def create_layout(self): self.place_and2_array() - spacing = self.wmask_en_len - self.and2.width - self.width = (self.num_wmasks*self.and2.width) + ((self.num_wmasks-1)*spacing) - self.height = self.and2.height self.add_layout_pins() self.add_boundary() self.DRC_LVS() def add_pins(self): for bit in range(self.num_wmasks): - self.add_pin("wmask_in_{}".format(bit),"INPUT") + self.add_pin("wmask_in_{}".format(bit), "INPUT") self.add_pin("en", "INPUT") for bit in range(self.num_wmasks): - self.add_pin("wmask_out_{}".format(bit),"OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin("wmask_out_{}".format(bit), "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): # Size the AND gate for the number of write drivers it drives, which is equal to the write size. # Assume stage effort of 3 to compute the size self.and2 = factory.create(module_type="pand2", - size=self.write_size/4.0) + size=self.write_size / 4.0) self.add_mod(self.and2) - def create_and2_array(self): self.and2_insts = {} for bit in range(self.num_wmasks): @@ -81,7 +74,6 @@ class write_mask_and_array(design.design): "wmask_out_{}".format(bit), "vdd", "gnd"]) - def place_and2_array(self): # Place the write mask AND array at the start of each write driver enable length. # This ensures the write mask AND array will be directly under the corresponding write driver enable wire. @@ -96,56 +88,45 @@ class write_mask_and_array(design.design): self.wmask_en_len = self.words_per_row * (self.write_size * self.driver_spacing) debug.check(self.wmask_en_len >= self.and2.width, - "Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width,self.wmask_en_len)) + "Write mask AND is wider than the corresponding write drivers {0} vs {1}.".format(self.and2.width, + self.wmask_en_len)) + self.width = self.bitcell.width * self.columns + self.height = self.and2.height + for i in range(self.num_wmasks): base = vector(i * self.wmask_en_len, 0) self.and2_insts[i].place(base) - def add_layout_pins(self): # Create the enable pin that connects all write mask AND array's B pins - beg_en_pin = self.and2_insts[0].get_pin("B") - end_en_pin = self.and2_insts[self.num_wmasks-1].get_pin("B") - if self.port % 2: - # Extend metal3 to edge of AND array in multiport - en_to_edge = self.and2.width - beg_en_pin.cx() - self.add_layout_pin(text="en", - layer="m3", - offset=beg_en_pin.bc(), - width=end_en_pin.cx() - beg_en_pin.cx() + en_to_edge) - self.add_via_center(layers=self.m1_stack, - offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) - self.add_via_center(layers=self.m2_stack, - offset=vector(end_en_pin.cx() + en_to_edge, end_en_pin.cy())) - else: - self.add_layout_pin(text="en", - layer="m3", - offset=beg_en_pin.bc(), - width=end_en_pin.cx() - beg_en_pin.cx()) + en_pin = self.and2_insts[0].get_pin("B") + self.add_layout_pin_segment_center(text="en", + layer="m3", + start=vector(0, en_pin.cy()), + end=vector(self.width, en_pin.cy())) for i in range(self.num_wmasks): # Copy remaining layout pins - self.copy_layout_pin(self.and2_insts[i],"A","wmask_in_{0}".format(i)) - self.copy_layout_pin(self.and2_insts[i],"Z","wmask_out_{0}".format(i)) + self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i)) + self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) # Add via connections to metal3 for AND array's B pin en_pin = self.and2_insts[i].get_pin("B") - self.add_via_center(layers=self.m1_stack, - offset=en_pin.center()) - self.add_via_center(layers=self.m2_stack, - offset=en_pin.center()) + en_pos = en_pin.center() + self.add_via_stack_center(from_layer=en_pin.layer, + to_layer="m3", + offset=en_pos) for supply in ["gnd", "vdd"]: supply_pin=self.and2_insts[i].get_pin(supply) self.add_power_pin(supply, supply_pin.center()) - for supply in ["gnd", "vdd"]: supply_pin_left = self.and2_insts[0].get_pin(supply) - supply_pin_right = self.and2_insts[self.num_wmasks-1].get_pin(supply) - self.add_path("m1",[supply_pin_left.lc(), supply_pin_right.rc()]) + supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply) + self.add_path("m1", [supply_pin_left.lc(), supply_pin_right.rc()]) def get_cin(self): """Get the relative capacitance of all the input connections in the bank""" diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 4c044f1c..69af1e62 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -44,6 +44,7 @@ class pand2(pgate.pgate): self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() self.add_boundary() self.DRC_LVS() @@ -76,26 +77,10 @@ class pand2(pgate.pgate): a2_pin = self.inv_inst.get_pin("A") mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("m1", + self.add_path(self.route_layer, [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="m1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="m1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=pin.layer, diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index f8cc2ac3..841ac69d 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -44,6 +44,7 @@ class pand3(pgate.pgate): self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() self.add_boundary() self.DRC_LVS() @@ -77,26 +78,10 @@ class pand3(pgate.pgate): a2_pin = self.inv_inst.get_pin("A") mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("m1", + self.add_path(z1_pin.layer, [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="m1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="m1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=pin.layer, diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index 6f9719eb..4d90286d 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -37,6 +37,7 @@ class pbuf(pgate.pgate): self.place_insts() self.add_wires() self.add_layout_pins() + self.route_supply_rails() self.add_boundary() def add_pins(self): @@ -78,26 +79,9 @@ class pbuf(pgate.pgate): # inv1 Z to inv2 A z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - mid_point = vector(z1_pin.cx(), a2_pin.cy()) - self.add_path("m1", [z1_pin.center(), mid_point, a2_pin.center()]) + self.add_zjog(self.route_layer, z1_pin.center(), a2_pin.center()) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv1_inst.get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="m1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv1_inst.get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="m1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - z_pin = self.inv2_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=z_pin.layer, diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 4bf654a4..9bba7aee 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -76,6 +76,7 @@ class pdriver(pgate.pgate): self.width = self.inv_inst_list[-1].rx() self.height = self.inv_inst_list[0].height + self.route_supply_rails() self.add_boundary() def add_pins(self): @@ -141,26 +142,11 @@ class pdriver(pgate.pgate): z_inst_list.append(self.inv_inst_list[x].get_pin("Z")) a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A")) mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy()) - self.add_path("m1", + self.add_path(self.route_layer, [z_inst_list[x].center(), mid_point, a_inst_list[x].center()]) def add_layout_pins(self): - # Continous vdd rail along with label. - vdd_pin = self.inv_inst_list[0].get_pin("vdd") - self.add_layout_pin(text="vdd", - layer="m1", - offset=vdd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) - - # Continous gnd rail along with label. - gnd_pin = self.inv_inst_list[0].get_pin("gnd") - self.add_layout_pin(text="gnd", - layer="m1", - offset=gnd_pin.ll().scale(0, 1), - width=self.width, - height=vdd_pin.height()) z_pin = self.inv_inst_list[len(self.inv_inst_list) - 1].get_pin("Z") self.add_layout_pin_rect_center(text="Z", diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index a942b35f..5351be07 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -17,6 +17,7 @@ from globals import OPTS if(OPTS.tech_name == "s8"): from tech import nmos_bins, pmos_bins, accuracy_requirement + class pgate(design.design): """ This is a module that implements some shared @@ -30,9 +31,25 @@ class pgate(design.design): if height: self.height = height elif not height: - # By default, we make it 10 M1 pitch tall - self.height = 10*self.m1_pitch + # By default, something simple + self.height = 14 * self.m1_pitch + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.route_layer_width = getattr(self, "{}_width".format(self.route_layer)) + self.route_layer_space = getattr(self, "{}_space".format(self.route_layer)) + self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer)) + + # This is the space from a S/D contact to the supply rail + # Assume the contact starts at the active edge + contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space + # This is a poly-to-poly of a flipped cell + poly_to_poly_gate_space = self.poly_extend_active + self.poly_space + self.top_bottom_space = max(contact_to_vdd_rail_space, + poly_to_poly_gate_space) + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -47,27 +64,27 @@ class pgate(design.design): """ Pure virtual function """ debug.error("Must over-ride create_layout.", -1) - def connect_pin_to_rail(self, inst, pin, supply): + def connect_pin_to_rail(self, inst, pin_name, supply_name): """ Connects a ptx pin to a supply rail. """ - source_pin = inst.get_pin(pin) - supply_pin = self.get_pin(supply) - if supply_pin.overlaps(source_pin): - return - - if supply == "gnd": - height = supply_pin.by() - source_pin.by() - elif supply == "vdd": - height = supply_pin.uy() - source_pin.by() - else: - debug.error("Invalid supply name.", -1) + supply_pin = self.get_pin(supply_name) - if abs(height) > 0: - self.add_rect(layer="m1", + source_pins = inst.get_pins(pin_name) + for source_pin in source_pins: + + if supply_name == "gnd": + height = supply_pin.by() - source_pin.by() + elif supply_name == "vdd": + height = supply_pin.uy() - source_pin.by() + else: + debug.error("Invalid supply name.", -1) + + debug.check(supply_pin.layer == source_pin.layer, "Supply pin is not on correct layer.") + self.add_rect(layer=source_pin.layer, offset=source_pin.ll(), height=height, width=source_pin.width()) - def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left"): + def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", directions=None): """ Route the input gate to the left side of the cell for access. Position specifies to place the contact the left, center, or @@ -93,8 +110,6 @@ class pgate(design.design): # Center is completely symmetric. contact_width = contact.poly_contact.width - contact_m1_width = contact.poly_contact.second_layer_width - contact_m1_height = contact.poly_contact.second_layer_height if position == "center": contact_offset = left_gate_offset \ @@ -111,18 +126,16 @@ class pgate(design.design): else: debug.error("Invalid contact placement option.", -1) - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=contact_offset) - - self.add_via_center(layers=self.poly_stack, - offset=contact_offset) + via = self.add_via_stack_center(from_layer="poly", + to_layer=self.route_layer, + offset=contact_offset, + directions=directions) self.add_layout_pin_rect_center(text=name, - layer="m1", + layer=self.route_layer, offset=contact_offset, - width=contact_m1_width, - height=contact_m1_height) + width=via.mod.second_layer_width, + height=via.mod.second_layer_height) # This is to ensure that the contact is # connected to the gate mid_point = contact_offset.scale(0.5, 1) \ @@ -194,12 +207,10 @@ class pgate(design.design): self.nwell_contact = self.add_via_center(layers=layer_stack, offset=contact_offset, implant_type="n", - well_type="n") - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=contact_offset) + well_type="n", + directions=("V", "V")) - self.add_rect_center(layer="m1", + self.add_rect_center(layer=self.route_layer, offset=contact_offset + vector(0, 0.5 * (self.height - contact_offset.y)), width=self.nwell_contact.mod.second_layer_width, height=self.height - contact_offset.y) @@ -249,13 +260,10 @@ class pgate(design.design): self.pwell_contact= self.add_via_center(layers=layer_stack, offset=contact_offset, implant_type="p", - well_type="p") - - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=contact_offset) - - self.add_rect_center(layer="m1", + well_type="p", + directions=("V", "V")) + + self.add_rect_center(layer=self.route_layer, offset=contact_offset.scale(1, 0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) @@ -279,6 +287,18 @@ class pgate(design.design): # offset=implant_offset, # width=implant_width, # height=implant_height) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) def determine_width(self): """ Determine the width based on the well contacts (assumed to be on the right side) """ @@ -286,9 +306,10 @@ class pgate(design.design): self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. - - def bin_width(self, tx_type, target_width): + @staticmethod + def bin_width(tx_type, target_width): + if tx_type == "nmos": bins = nmos_bins[drc("minwidth_poly")] elif tx_type == "pmos": @@ -303,22 +324,24 @@ class pgate(design.design): scaled_bin = bins[0] * scaling_factor else: + base_bins = [] scaled_bins = [] scaling_factors = [] scaled_bins.append(bins[-1]) + base_bins.append(bins[-1]) scaling_factors.append(1) for width in bins[0:-1]: m = math.ceil(target_width / width) + base_bins.append(width) scaling_factors.append(m) scaled_bins.append(m * width) select = bisect_left(scaled_bins, target_width) scaling_factor = scaling_factors[select] scaled_bin = scaled_bins[select] - select = (select + 1) % len(scaled_bins) - selected_bin = bins[select] + selected_bin = base_bins[select] - debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, scaled_bin, target_width)) + debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, selected_bin * scaling_factor, target_width)) return(selected_bin, scaling_factor) @@ -343,4 +366,4 @@ class pgate(design.design): return(scaled_bins) def bin_accuracy(self, ideal_width, width): - return abs(1-(ideal_width - width)/ideal_width) \ No newline at end of file + return abs(1-(ideal_width - width)/ideal_width) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 0a26a2fd..e0e1ce81 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -22,17 +22,17 @@ from errors import drc_error if(OPTS.tech_name == "s8"): from tech import nmos_bins, pmos_bins, accuracy_requirement + class pinv(pgate.pgate): """ Pinv generates gds of a parametrically sized inverter. The size is specified as the drive size (relative to minimum NMOS) and a beta value for choosing the pmos size. The inverter's cell height is usually the same as the 6t library cell and is measured - from center of rail to rail.. The route_output will route the - output to the right side of the cell for easier access. + from center of rail to rail. """ - def __init__(self, name, size=1, beta=parameter["beta"], height=None, route_output=True): + def __init__(self, name, size=1, beta=parameter["beta"], height=None): debug.info(2, "creating pinv structure {0} with size of {1}".format(name, @@ -43,7 +43,6 @@ class pinv(pgate.pgate): self.nmos_size = size self.pmos_size = beta * size self.beta = beta - self.route_output = False pgate.pgate.__init__(self, name, height) @@ -108,13 +107,6 @@ class pinv(pgate.pgate): min_channel = max(contact.poly_contact.width + self.m1_space, contact.poly_contact.width + 2 * self.poly_to_active) - # This is the extra space needed to ensure DRC rules - # to the active contacts - 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) - 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, @@ -202,30 +194,22 @@ class pinv(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer, connect_poly=True, - connect_active=True) + connect_drain_active=True) self.add_mod(self.nmos) self.pmos = factory.create(module_type="ptx", width=self.pmos_width, mults=self.tx_mults, tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer, connect_poly=True, - connect_active=True) + connect_drain_active=True) self.add_mod(self.pmos) - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="m1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="m1", - offset=vector(0.5 * self.width, self.height), - width=self.width) - def create_ptx(self): """ Create the PMOS and NMOS netlist. @@ -266,7 +250,7 @@ class pinv(pgate.pgate): Route the output (drains) together. Optionally, routes output to edge. """ - + # Get the drain pins nmos_drain_pin = self.nmos_inst.get_pin("D") pmos_drain_pin = self.pmos_inst.get_pin("D") @@ -274,24 +258,16 @@ class pinv(pgate.pgate): # Pick point at right most of NMOS and connect down to PMOS nmos_drain_pos = nmos_drain_pin.bc() pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y) - self.add_path("m1", [nmos_drain_pos, pmos_drain_pos]) + self.add_path(self.route_layer, [nmos_drain_pos, pmos_drain_pos]) # Remember the mid for the output mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y) - if self.route_output: - # This extends the output to the edge of the cell - output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0) - self.add_layout_pin_segment_center(text="Z", - layer="m1", - start=mid_drain_offset, - end=output_offset) - else: - # This leaves the output as an internal pin (min sized) - self.add_layout_pin_rect_center(text="Z", - layer="m1", - offset=mid_drain_offset \ - + vector(0.5 * self.m1_width, 0)) + # This leaves the output as an internal pin (min sized) + output_offset = mid_drain_offset + vector(0.5 * self.route_layer_width, 0) + self.add_layout_pin_rect_center(text="Z", + layer=self.route_layer, + offset=output_offset) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 08024cea..5d20201e 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -72,65 +72,45 @@ class pnand2(pgate.pgate): def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos_nd = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - add_drain_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos_nd) + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.nmos_left) - self.nmos_ns = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - add_source_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos_ns) + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) - self.pmos = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos) + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_left) + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ - # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width, - self.m1_space + contact.m1_via.first_layer_width, - self.m2_space + contact.m2_via.first_layer_width, - self.m3_space + contact.m2_via.second_layer_width) - - # Compute the other pmos2 location, # but determining offset to overlap the # source and drain pins - self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() - - # This is the extra space needed to ensure DRC rules - # to the active contacts - extra_contact_space = max(-self.nmos_nd.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) - - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="m1", - offset=vector(0.5*self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="m1", - offset=vector(0.5 * self.width, self.height), - width=self.width) + self.overlap_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center() def create_ptx(self): """ @@ -138,19 +118,19 @@ class pnand2(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnand2_pmos1", - mod=self.pmos) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand2_pmos2", - mod=self.pmos) + mod=self.pmos_right) self.connect_inst(["Z", "B", "vdd", "vdd"]) self.nmos1_inst = self.add_inst(name="pnand2_nmos1", - mod=self.nmos_nd) + mod=self.nmos_left) self.connect_inst(["Z", "B", "net1", "gnd"]) self.nmos2_inst = self.add_inst(name="pnand2_nmos2", - mod=self.nmos_ns) + mod=self.nmos_right) self.connect_inst(["net1", "A", "gnd", "gnd"]) def place_ptx(self): @@ -159,35 +139,29 @@ class pnand2(pgate.pgate): to provide maximum routing in channel """ - pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height \ + pmos1_pos = vector(self.pmos_left.active_offset.x, + self.height - self.pmos_left.active_height \ - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset self.pmos2_inst.place(self.pmos2_pos) - nmos1_pos = vector(self.pmos.active_offset.x, + nmos1_pos = vector(self.pmos_left.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0, - 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos_nd.active_height)) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies AFTER the wells are created """ - self.add_nwell_contact(self.pmos, - self.pmos2_pos + vector(self.m1_pitch, 0)) - self.add_pwell_contact(self.nmos_nd, - self.nmos2_pos + vector(self.m1_pitch, 0)) + self.add_nwell_contact(self.pmos_right, self.pmos2_pos) + self.add_pwell_contact(self.nmos_left, self.nmos2_pos) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -200,35 +174,46 @@ class pnand2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ - inputB_yoffset = self.nmos2_inst.uy() + 0.5 * contact.poly_contact.height + + + # Top of NMOS drain + nmos_pin = self.nmos2_inst.get_pin("D") + bottom_pin_offset = nmos_pin.uy() + self.inputA_yoffset = bottom_pin_offset + self.m1_pitch + + self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch + + # This will help with the wells and the input/output placement self.route_input_gate(self.pmos2_inst, self.nmos2_inst, - inputB_yoffset, + self.inputB_yoffset, "B", position="center") - # This will help with the wells and the input/output placement - self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \ - - contact.poly_contact.height self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, - "A") + "A", + position="center") def route_output(self): """ Route the Z output """ + + # One routing track layer below the PMOS contacts + route_layer_offset = 0.5 * self.route_layer_width + self.route_layer_space + output_yoffset = self.pmos1_inst.get_pin("D").by() - route_layer_offset + # PMOS1 drain pmos_pin = self.pmos1_inst.get_pin("D") - top_pin_offset = pmos_pin.center() + top_pin_offset = pmos_pin.bc() # NMOS2 drain nmos_pin = self.nmos2_inst.get_pin("D") - bottom_pin_offset = nmos_pin.center() + bottom_pin_offset = nmos_pin.uc() # Output pin - c_pin = self.get_pin("B") - out_offset = vector(c_pin.cx() + self.m1_pitch, - self.inputA_yoffset) + out_offset = vector(nmos_pin.cx() + self.route_layer_pitch, + output_yoffset) # This routes on M2 # # Midpoints of the L routes go horizontal first then vertical @@ -251,27 +236,22 @@ class pnand2(pgate.pgate): # [top_pin_offset, mid1_offset, out_offset, # mid2_offset, bottom_pin_offset]) - # This routes on M1 + # This routes on route_layer # Midpoints of the L routes goes vertical first then horizontal - mid1_offset = vector(top_pin_offset.x, out_offset.y) - # Midpoints of the L routes goes horizontal first then vertical - mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + top_mid_offset = vector(top_pin_offset.x, out_offset.y) + # Top transistors + self.add_path(self.route_layer, + [top_pin_offset, top_mid_offset, out_offset]) - self.add_path("m1", - [top_pin_offset, mid1_offset, out_offset]) - # Route in two segments to have the width rule - self.add_path("m1", - [bottom_pin_offset, mid2_offset + vector(0.5 * self.m1_width, 0)], - width=nmos_pin.height()) - self.add_path("m1", - [mid2_offset, out_offset]) + bottom_mid_offset = bottom_pin_offset + vector(0, self.route_layer_pitch) + # Bottom transistors + self.add_path(self.route_layer, + [out_offset, bottom_mid_offset, bottom_pin_offset]) # This extends the output to the edge of the cell self.add_layout_pin_rect_center(text="Z", - layer="m1", - offset=out_offset, - width=contact.m1_via.first_layer_width, - height=contact.m1_via.first_layer_height) + layer=self.route_layer, + offset=out_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 5f047fc4..454b75dd 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import contact import pgate import debug from tech import drc, parameter, spice @@ -14,6 +13,7 @@ import logical_effort from sram_factory import factory from globals import OPTS + class pnand3(pgate.pgate): """ This module generates gds of a parametrically sized 2-input nand. @@ -74,68 +74,64 @@ class pnand3(pgate.pgate): def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos_nsnd = factory.create(module_type="ptx", + self.nmos_center = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact="active") + self.add_mod(self.nmos_center) + + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) + + self.nmos_left = factory.create(module_type="ptx", width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", - add_source_contact=False, - add_drain_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos_nsnd) - - self.nmos_ns = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - add_source_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos_ns) - - self.nmos_nd = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - add_drain_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos_nd) + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.nmos_left) - self.pmos = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos) + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_left) + self.pmos_center = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_center) + + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ # Compute the overlap of the source and drain pins - overlap_xoffset = self.pmos.get_pin("D").ll().x - self.pmos.get_pin("S").ll().x - self.ptx_offset = vector(overlap_xoffset, 0) + self.ptx_offset = self.pmos_left.get_pin("D").center() - self.pmos_left.get_pin("S").center() # This is the extra space needed to ensure DRC rules # to the active contacts 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) - - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="m1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="m1", - offset=vector(0.5 * self.width, self.height), - width=self.width) def create_ptx(self): """ @@ -143,27 +139,27 @@ class pnand3(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnand3_pmos1", - mod=self.pmos) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand3_pmos2", - mod=self.pmos) + mod=self.pmos_center) self.connect_inst(["Z", "B", "vdd", "vdd"]) self.pmos3_inst = self.add_inst(name="pnand3_pmos3", - mod=self.pmos) + mod=self.pmos_right) self.connect_inst(["Z", "C", "vdd", "vdd"]) self.nmos1_inst = self.add_inst(name="pnand3_nmos1", - mod=self.nmos_nd) + mod=self.nmos_left) self.connect_inst(["Z", "C", "net1", "gnd"]) self.nmos2_inst = self.add_inst(name="pnand3_nmos2", - mod=self.nmos_nsnd) + mod=self.nmos_center) self.connect_inst(["net1", "B", "net2", "gnd"]) self.nmos3_inst = self.add_inst(name="pnand3_nmos3", - mod=self.nmos_ns) + mod=self.nmos_right) self.connect_inst(["net2", "A", "gnd", "gnd"]) def place_ptx(self): @@ -172,8 +168,8 @@ class pnand3(pgate.pgate): and lowest position to provide maximum routing in channel """ - pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height - self.top_bottom_space) + pmos1_pos = vector(self.pmos_left.active_offset.x, + self.height - self.pmos_left.active_height - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) pmos2_pos = pmos1_pos + self.ptx_offset @@ -182,7 +178,7 @@ class pnand3(pgate.pgate): self.pmos3_pos = pmos2_pos + self.ptx_offset self.pmos3_inst.place(self.pmos3_pos) - nmos1_pos = vector(self.pmos.active_offset.x, + nmos1_pos = vector(self.pmos_left.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) @@ -195,9 +191,9 @@ class pnand3(pgate.pgate): def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ - self.add_nwell_contact(self.pmos, + self.add_nwell_contact(self.pmos_right, self.pmos3_pos + vector(self.m1_pitch, 0)) - self.add_pwell_contact(self.nmos_ns, + self.add_pwell_contact(self.nmos_right, self.nmos3_pos + vector(self.m1_pitch, 0)) def connect_rails(self): @@ -212,37 +208,34 @@ class pnand3(pgate.pgate): def route_inputs(self): """ Route the A and B and C inputs """ - m1_pitch = self.m1_space + contact.m1_via.first_layer_height - # Put B right on the well line - self.inputB_yoffset = self.nwell_y_offset - self.route_input_gate(self.pmos2_inst, - self.nmos2_inst, - self.inputB_yoffset, - "B", - position="center") - - # FIXME: constant hack - self.inputC_yoffset = self.inputB_yoffset - 1.15 * m1_pitch - self.route_input_gate(self.pmos3_inst, - self.nmos3_inst, - self.inputC_yoffset, - "C", - position="right") + pmos_drain_bottom = self.pmos1_inst.get_pin("D").by() + self.output_yoffset = pmos_drain_bottom - 0.5 * self.route_layer_width - self.route_layer_space - # FIXME: constant hack - if OPTS.tech_name == "s8": - self.inputA_yoffset = self.inputB_yoffset + 1.15 * m1_pitch - else: - self.inputA_yoffset = self.inputB_yoffset + 1.12 * m1_pitch + self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A", position="left") + + # Put B right on the well line + self.inputB_yoffset = self.inputA_yoffset - self.m1_pitch + self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + self.inputB_yoffset, + "B", + position="center") + + self.inputC_yoffset = self.inputB_yoffset - self.m1_pitch + self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + self.inputC_yoffset, + "C", + position="right") def route_output(self): """ Route the Z output """ - + # PMOS1 drain pmos1_pin = self.pmos1_inst.get_pin("D") # PMOS3 drain @@ -250,14 +243,9 @@ class pnand3(pgate.pgate): # NMOS3 drain nmos3_pin = self.nmos3_inst.get_pin("D") - # midpoint for routing - mid_offset = vector(nmos3_pin.cx() + self.m1_pitch, - self.inputA_yoffset) + out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch, + self.output_yoffset) - # Aligned with the well taps - out_offset = vector(self.nwell_contact.cx(), - self.inputA_yoffset) - # Go up to metal2 for ease on all output pins # self.add_via_center(layers=self.m1_stack, # offset=pmos1_pin.center(), @@ -282,26 +270,24 @@ class pnand3(pgate.pgate): bottom_pin_offset = nmos3_pin.center() # PMOS1 to output - self.add_path("m1", [top_left_pin_offset, - vector(top_left_pin_offset.x, out_offset.y), - out_offset]) + self.add_path(self.route_layer, [top_left_pin_offset, + vector(top_left_pin_offset.x, out_offset.y), + out_offset]) # PMOS3 to output - self.add_path("m1", [top_right_pin_offset, - vector(top_right_pin_offset.x, mid_offset.y), - mid_offset]) + self.add_path(self.route_layer, [top_right_pin_offset, + vector(top_right_pin_offset.x, out_offset.y), + out_offset]) # NMOS3 to output - mid2_offset = vector(mid_offset.x, bottom_pin_offset.y) - self.add_path("m1", + mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + self.add_path(self.route_layer, [bottom_pin_offset, mid2_offset], width=nmos3_pin.height()) - mid3_offset = vector(mid_offset.x, nmos3_pin.by()) - self.add_path("m1", [mid3_offset, mid_offset]) + mid3_offset = vector(out_offset.x, nmos3_pin.by()) + self.add_path(self.route_layer, [mid3_offset, out_offset]) self.add_layout_pin_rect_center(text="Z", - layer="m1", - offset=out_offset, - width=contact.m1_via.first_layer_width, - height=contact.m1_via.first_layer_height) + layer=self.route_layer, + offset=out_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 225a795c..f522578e 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -71,74 +71,54 @@ class pnor2(pgate.pgate): def add_ptx(self): """ Create the PMOS and NMOS transistors. """ - self.nmos = factory.create(module_type="ptx", - width=self.nmos_width, - mults=self.tx_mults, - tx_type="nmos", - connect_poly=True, - connect_active=True) - self.add_mod(self.nmos) + self.nmos_left = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_left) - self.pmos_nd = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - add_drain_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos_nd) + self.nmos_right = factory.create(module_type="ptx", + width=self.nmos_width, + mults=self.tx_mults, + tx_type="nmos", + add_source_contact=self.route_layer, + add_drain_contact=self.route_layer) + self.add_mod(self.nmos_right) + + self.pmos_left = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact=self.route_layer, + add_drain_contact="active") + self.add_mod(self.pmos_left) - self.pmos_ns = factory.create(module_type="ptx", - width=self.pmos_width, - mults=self.tx_mults, - tx_type="pmos", - add_source_contact=False, - connect_poly=True, - connect_active=True) - self.add_mod(self.pmos_ns) + self.pmos_right = factory.create(module_type="ptx", + width=self.pmos_width, + mults=self.tx_mults, + tx_type="pmos", + add_source_contact="active", + add_drain_contact=self.route_layer) + self.add_mod(self.pmos_right) def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ - # metal spacing to allow contacts on any layer - self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width, - self.m1_space + contact.m1_via.first_layer_width, - self.m2_space + contact.m2_via.first_layer_width, - self.m3_space + contact.m2_via.second_layer_width) - # Compute the other pmos2 location, but determining # offset to overlap the source and drain pins - self.overlap_offset = self.pmos_ns.get_pin("D").ll() - self.pmos_nd.get_pin("S").ll() + self.overlap_offset = self.pmos_right.get_pin("D").center() - self.pmos_left.get_pin("S").center() # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.width = 2 * self.pmos_ns.active_width \ - + self.pmos_ns.active_contact.width \ + self.width = 2 * self.pmos_right.active_width \ + + self.pmos_right.active_contact.width \ + 2 * self.active_space \ + 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 - # to the active contacts - 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) - def route_supply_rails(self): - """ Add vdd/gnd rails to the top and bottom. """ - self.add_layout_pin_rect_center(text="gnd", - layer="m1", - offset=vector(0.5 * self.width, 0), - width=self.width) - - self.add_layout_pin_rect_center(text="vdd", - layer="m1", - offset=vector(0.5 * self.width, self.height), - width=self.width) - def create_ptx(self): """ Add PMOS and NMOS to the layout at the upper-most and lowest position @@ -146,19 +126,19 @@ class pnor2(pgate.pgate): """ self.pmos1_inst = self.add_inst(name="pnor2_pmos1", - mod=self.pmos_nd) + mod=self.pmos_left) self.connect_inst(["vdd", "A", "net1", "vdd"]) self.pmos2_inst = self.add_inst(name="pnor2_pmos2", - mod=self.pmos_ns) + mod=self.pmos_right) self.connect_inst(["net1", "B", "Z", "vdd"]) self.nmos1_inst = self.add_inst(name="pnor2_nmos1", - mod=self.nmos) + mod=self.nmos_left) self.connect_inst(["Z", "A", "gnd", "gnd"]) self.nmos2_inst = self.add_inst(name="pnor2_nmos2", - mod=self.nmos) + mod=self.nmos_right) self.connect_inst(["Z", "B", "gnd", "gnd"]) def place_ptx(self): @@ -166,30 +146,35 @@ class pnor2(pgate.pgate): Add PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ + # Some of the S/D contacts may extend beyond the active, + # but this needs to be done in the gate itself + contact_extend_active_space = max(-self.nmos_right.get_pin("D").by(), 0) + # Assume the contact starts at the active edge + contact_to_vdd_rail_space = 0.5 * self.m1_width + self.m1_space + contact_extend_active_space + # This is a poly-to-poly of a flipped cell + poly_to_poly_gate_space = self.poly_extend_active + self.poly_space + # Recompute this since it has a small txwith the added contact extend active spacing + self.top_bottom_space = max(contact_to_vdd_rail_space, + poly_to_poly_gate_space) - pmos1_pos = vector(self.pmos_ns.active_offset.x, - self.height - self.pmos_ns.active_height \ - - self.top_bottom_space) + pmos1_pos = vector(self.pmos_right.active_offset.x, + self.height - self.pmos_right.active_height - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset self.pmos2_inst.place(self.pmos2_pos) - nmos1_pos = vector(self.pmos_ns.active_offset.x, self.top_bottom_space) + nmos1_pos = vector(self.pmos_right.active_offset.x, self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0, - 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ - self.add_nwell_contact(self.pmos_ns, self.pmos2_pos) - self.add_pwell_contact(self.nmos, self.nmos2_pos) + self.add_nwell_contact(self.pmos_right, self.pmos2_pos) + self.add_pwell_contact(self.nmos_right, self.nmos2_pos) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -202,53 +187,53 @@ class pnor2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ - # Use M2 spaces so we can drop vias on the pins later! - inputB_yoffset = self.nmos2_inst.uy() + contact.poly_contact.height + + # Top of NMOS drain + nmos_pin = self.nmos2_inst.get_pin("D") + bottom_pin_offset = nmos_pin.uy() + self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch + self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch + self.route_input_gate(self.pmos2_inst, self.nmos2_inst, - inputB_yoffset, + self.inputB_yoffset, "B", - position="center") + position="right", + directions=("V", "V")) # This will help with the wells and the input/output placement - self.inputA_yoffset = inputB_yoffset + self.input_spacing self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, - "A") + "A", + directions=("V", "V")) + + self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch def route_output(self): """ Route the Z output """ - # PMOS2 drain + # PMOS2 (right) drain pmos_pin = self.pmos2_inst.get_pin("D") - # NMOS1 drain + # NMOS1 (left) drain nmos_pin = self.nmos1_inst.get_pin("D") - # NMOS2 drain (for output via placement) + # NMOS2 (right) drain (for output via placement) nmos2_pin = self.nmos2_inst.get_pin("D") # Go up to metal2 for ease on all output pins - self.add_via_center(layers=self.m1_stack, - offset=pmos_pin.center()) - m1m2_contact = self.add_via_center(layers=self.m1_stack, - offset=nmos_pin.center()) - - mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y) - mid2_offset = vector(pmos_pin.center().x, self.inputA_yoffset) - mid3_offset = mid2_offset + vector(self.m2_width, 0) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos_pin.center()) + # m1m2_contact = self.add_via_center(layers=self.m1_stack, + # offset=nmos_pin.center()) + mid1_offset = vector(nmos_pin.center().x, self.output_yoffset) + mid2_offset = vector(pmos_pin.center().x, self.output_yoffset) + # PMOS1 to mid-drain to NMOS2 drain - self.add_path("m2", - [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 - self.add_via_center(layers=self.m1_stack, - offset=mid3_offset) + self.add_path(self.route_layer, + [nmos_pin.center(), mid1_offset, mid2_offset, pmos_pin.center()]) self.add_layout_pin_rect_center(text="Z", - layer="m1", - offset=mid3_offset, - width=contact.m1_via.first_layer_height, - height=contact.m1_via.first_layer_width) + layer=self.route_layer, + offset=mid2_offset) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 6985efeb..d47e445f 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -81,7 +81,7 @@ class precharge(design.design): Initializes the upper and lower pmos """ if(OPTS.tech_name == "s8"): - (self.ptx_width, self.ptx_mults) = pgate.bin_width(self, "pmos", self.ptx_width) + (self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width) self.pmos = factory.create(module_type="ptx", width=self.ptx_width, mults=self.ptx_mults, @@ -114,7 +114,7 @@ class precharge(design.design): self.add_power_pin("vdd", self.well_contact_pos, - vertical=True) + directions=("V", "V")) # Hack for li layers if hasattr(self, "li_stack"): @@ -159,7 +159,7 @@ class precharge(design.design): self.lower_pmos_inst.place(self.lower_pmos_position) # adds the upper pmos(s) to layout with 2 M2 tracks - ydiff = self.pmos.height + self.m2_pitch + ydiff = self.pmos.height + 2 * self.m2_pitch self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff) self.upper_pmos1_inst.place(self.upper_pmos1_pos) diff --git a/compiler/pgates/ptristate_inv.py b/compiler/pgates/ptristate_inv.py index 22f6b164..9fd5f8b6 100644 --- a/compiler/pgates/ptristate_inv.py +++ b/compiler/pgates/ptristate_inv.py @@ -79,9 +79,6 @@ class ptristate_inv(pgate.pgate): self.width = self.well_width + 0.5 * self.m1_space # Height is an input parameter, so it is not recomputed. - # Make sure we can put a well above and below - self.top_bottom_space = max(contact.active_contact.width, contact.active_contact.height) - def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index b4b20381..c584e70b 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -14,7 +14,7 @@ import contact import logical_effort import os from globals import OPTS - +from pgate import pgate class ptx(design.design): """ @@ -23,32 +23,47 @@ class ptx(design.design): the transistor width. Mults is the number of transistors of the given width. Total width is therefore mults*width. Options allow you to connect the fingered gates and active for parallel devices. - + The add_*_contact option tells which layer to bring source/drain up to. """ def __init__(self, name="", width=drc("minwidth_tx"), mults=1, tx_type="nmos", - add_source_contact=True, - add_drain_contact=True, + add_source_contact=None, + add_drain_contact=None, series_devices=False, - connect_active=False, + connect_drain_active=False, + connect_source_active=False, connect_poly=False, num_contacts=None): + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + # Default contacts are the lowest layer + if not add_source_contact: + add_source_contact = self.route_layer + + # Default contacts are the lowest layer + if not add_drain_contact: + add_drain_contact = self.route_layer + # We need to keep unique names because outputting to GDSII # will use the last record with a given name. I.e., you will # over-write a design in GDS if one has and the other doesn't # have poly connected, for example. name = "{0}_m{1}_w{2:.3f}".format(tx_type, mults, width) - if not add_source_contact: - name += "_ns" - if not add_drain_contact: - name += "_nd" + name += "_s{}".format(add_source_contact) + name += "_d{}".format(add_drain_contact) if series_devices: name += "_sd" - if connect_active: - name += "_a" + if connect_drain_active: + name += "_da" + if connect_source_active: + name += "_sa" if connect_poly: name += "_p" if num_contacts: @@ -61,13 +76,17 @@ class ptx(design.design): self.tx_type = tx_type self.mults = mults self.tx_width = width - self.connect_active = connect_active + self.connect_drain_active = connect_drain_active + self.connect_source_active = connect_source_active self.connect_poly = connect_poly self.add_source_contact = add_source_contact self.add_drain_contact = add_drain_contact self.series_devices = series_devices self.num_contacts = num_contacts + self.route_layer_width = drc("minwidth_{}".format(self.route_layer)) + self.route_layer_space = drc("{0}_to_{0}".format(self.route_layer)) + # Since it has variable height, it is not a pgate. self.create_netlist() # We must always create ptx layout for pbitcell @@ -109,6 +128,7 @@ class ptx(design.design): perimeter_sd = 2 * self.poly_width + 2 * self.tx_width if OPTS.tech_name == "s8": # s8 technology is in microns + (self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], self.mults, self.tx_width, @@ -265,55 +285,42 @@ class ptx(design.design): width=poly_width, height=self.poly_width) - def connect_fingered_active(self, drain_positions, source_positions): + def connect_fingered_active(self, positions, pin_name, top): """ Connect each contact up/down to a source or drain pin """ - + + if len(positions) <= 1: + return + + layer_space = getattr(self, "{}_space".format(self.route_layer)) + layer_width = getattr(self, "{}_width".format(self.route_layer)) + # This is the distance that we must route up or down from the center # of the contacts to avoid DRC violations to the other contacts pin_offset = vector(0, - 0.5 * self.active_contact.second_layer_height + self.m1_space + 0.5 * self.m1_width) + 0.5 * self.active_contact.second_layer_height + layer_space + 0.5 * layer_width) # This is the width of a m1 extend the ends of the pin - end_offset = vector(self.m1_width / 2.0, 0) + end_offset = vector(layer_width / 2.0, 0) - # drains always go to the MIDDLE of the cell, - # so top of NMOS, bottom of PMOS - # so reverse the directions for NMOS compared to PMOS. - if self.tx_type == "pmos": - drain_dir = -1 - source_dir = 1 + # We move the opposite direction from the bottom + if not top: + offset = pin_offset.scale(-1, -1) else: - drain_dir = 1 - source_dir = -1 - - if len(source_positions) > 1: - source_offset = pin_offset.scale(source_dir, source_dir) - # remove the individual connections - self.remove_layout_pin("S") - # Add each vertical segment - for a in source_positions: - self.add_path(("m1"), - [a, a + pin_offset.scale(source_dir, - source_dir)]) - # Add a single horizontal pin - self.add_layout_pin_segment_center(text="S", - layer="m1", - start=source_positions[0] + source_offset - end_offset, - end=source_positions[-1] + source_offset + end_offset) + offset = pin_offset + + # remove the individual connections + self.remove_layout_pin(pin_name) + # Add each vertical segment + for a in positions: + self.add_path(self.route_layer, + [a, a + offset]) + # Add a single horizontal pin + self.add_layout_pin_segment_center(text=pin_name, + layer=self.route_layer, + start=positions[0] + offset - end_offset, + end=positions[-1] + offset + end_offset) - if len(drain_positions)>1: - drain_offset = pin_offset.scale(drain_dir,drain_dir) - self.remove_layout_pin("D") # remove the individual connections - # Add each vertical segment - for a in drain_positions: - self.add_path(("m1"), [a,a+drain_offset]) - # Add a single horizontal pin - self.add_layout_pin_segment_center(text="D", - layer="m1", - start=drain_positions[0] + drain_offset - end_offset, - end=drain_positions[-1] + drain_offset + end_offset) - def add_poly(self): """ Add the poly gates(s) and (optionally) connect them. @@ -432,12 +439,12 @@ class ptx(design.design): label = "S" source_positions.append(pos) - if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact): + if (label=="S" and self.add_source_contact): contact = self.add_diff_contact(label, pos) - if label == "S": - self.source_contacts.append(contact) - else: - self.drain_contacts.append(contact) + self.source_contacts.append(contact) + elif (label=="D" and self.add_drain_contact): + contact = self.add_diff_contact(label, pos) + self.drain_contacts.append(contact) else: self.add_layout_pin_rect_center(text=label, layer="active", @@ -453,19 +460,22 @@ class ptx(design.design): label = "S" source_positions.append(pos) - if (label=="S" and self.add_source_contact) or (label=="D" and self.add_drain_contact): + if (label=="S" and self.add_source_contact): contact = self.add_diff_contact(label, pos) - if label == "S": - self.source_contacts.append(contact) - else: - self.drain_contacts.append(contact) + self.source_contacts.append(contact) + elif (label=="D" and self.add_drain_contact): + contact = self.add_diff_contact(label, pos) + self.drain_contacts.append(contact) else: self.add_layout_pin_rect_center(text=label, layer="active", offset=pos) - if self.connect_active: - self.connect_fingered_active(drain_positions, source_positions) + if self.connect_source_active: + self.connect_fingered_active(source_positions, "S", top=(self.tx_type=="pmos")) + + if self.connect_drain_active: + self.connect_fingered_active(drain_positions, "D", top=(self.tx_type=="nmos")) def get_stage_effort(self, cout): """Returns an object representing the parameters for delay in tau units.""" @@ -488,34 +498,39 @@ class ptx(design.design): return self.mults * self.tx_width / drc("minwidth_tx") def add_diff_contact(self, label, pos): - contact=self.add_via_center(layers=self.active_stack, - offset=pos, - size=(1, self.num_contacts), - directions=("V", "V"), - implant_type=self.implant_type, - well_type=self.well_type) - - if hasattr(self, "li_stack"): - contact=self.add_via_center(layers=self.li_stack, - offset=pos, - directions=("V", "V")) - # contact_area = contact.mod.second_layer_width * contact.mod.second_layer_height - # min_area = drc("minarea_m1") - # width = contact.mod.second_layer_width - # if contact_area < min_area: - # height = min_area / width - # else: - # height = contact.mod.second_layer_height - width = contact.mod.second_layer_width - height = contact.mod.second_layer_height + if label == "S": + layer = self.add_source_contact + elif label == "D": + layer = self.add_drain_contact + else: + debug.error("Invalid source drain name.") + + if layer != "active": + via=self.add_via_stack_center(offset=pos, + from_layer="active", + to_layer=layer, + size=(1, self.num_contacts), + directions=("V", "V"), + implant_type=self.implant_type, + well_type=self.well_type) + + pin_height = via.mod.second_layer_height + pin_width = via.mod.second_layer_width + else: + via = None + + pin_height = None + pin_width = None + + # Source drain vias are all vertical self.add_layout_pin_rect_center(text=label, - layer="m1", + layer=layer, offset=pos, - width=width, - height=height) + width=pin_width, + height=pin_height) - return(contact) + return(via) def get_cin(self): """Returns the relative gate cin of the tx""" diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index e0f277e9..27156eeb 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -18,17 +18,13 @@ class router_tech: """ def __init__(self, layers, rail_track_width): """ - Allows us to change the layers that we are routing on. First layer - is always horizontal, middle is via, and last is always - vertical. + Allows us to change the layers that we are routing on. + This uses the preferreed directions. """ self.layers = layers self.rail_track_width = rail_track_width if len(self.layers) == 1: - if preferred_directions[self.layers[0]] != "H": - debug.warning("Using '{}' for horizontal routing, but it " \ - "prefers vertical routing".format(self.layers[0])) self.horiz_layer_name = self.vert_layer_name = self.layers[0] self.horiz_lpp = self.vert_lpp = layer[self.layers[0]] @@ -42,13 +38,21 @@ class router_tech: # figure out wich of the two layers prefers horizontal/vertical # routing - if preferred_directions[try_horiz_layer] == "H" and preferred_directions[try_vert_layer] == "V": + self.horiz_layer_name = None + self.vert_layer_name = None + + if preferred_directions[try_horiz_layer] == "H": self.horiz_layer_name = try_horiz_layer + else: + self.horiz_layer_name = try_vert_layer + if preferred_directions[try_vert_layer] == "V": self.vert_layer_name = try_vert_layer else: - raise ValueError("Layer '{}' and '{}' are using the wrong " \ - "preferred_directions '{}' and '{}'. Only "\ - "('H', 'V') are supported") + self.vert_layer_name = try_horiz_layer + + if not self.horiz_layer_name or not self.vert_layer_name: + raise ValueError("Layer '{}' and '{}' are using the wrong " + "preferred_directions '{}' and '{}'.") via_connect = contact(self.layers, (1, 1)) max_via_size = max(via_connect.width,via_connect.height) diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index b28f875e..cd7b6b7b 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -5,21 +5,15 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import gdsMill -import tech -import math import debug -from globals import OPTS,print_time -from contact import contact -from pin_group import pin_group -from pin_layout import pin_layout -from vector3d import vector3d +from globals import print_time +from vector3d import vector3d from router import router from direction import direction from datetime import datetime -import grid import grid_utils + class supply_grid_router(router): """ A router class to read an obstruction map from a gds and @@ -44,14 +38,13 @@ class supply_grid_router(router): self.supply_rail_tracks = {} print_time("Init supply router", datetime.now(), start_time, 3) - def create_routing_grid(self): """ Create a sprase routing grid with A* expansion functions. """ size = self.ur - self.ll - debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) + debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) import supply_grid self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width) @@ -60,12 +53,12 @@ class supply_grid_router(router): """ Add power supply rails and connect all pins to these rails. """ - debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) + debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) self.vdd_name = vdd_name self.gnd_name = gnd_name # Clear the pins if we have previously routed - if (hasattr(self,'rg')): + if (hasattr(self, 'rg')): self.clear_pins() else: # Creat a routing grid over the entire area @@ -76,32 +69,32 @@ class supply_grid_router(router): # Get the pin shapes start_time = datetime.now() self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) - print_time("Finding pins and blockages",datetime.now(), start_time, 3) + print_time("Finding pins and blockages", datetime.now(), start_time, 3) # Add the supply rails in a mesh network and connect H/V with vias start_time = datetime.now() # Block everything self.prepare_blockages(self.gnd_name) # Determine the rail locations - self.route_supply_rails(self.gnd_name,0) + self.route_supply_rails(self.gnd_name, 0) # Block everything self.prepare_blockages(self.vdd_name) # Determine the rail locations - self.route_supply_rails(self.vdd_name,1) - print_time("Routing supply rails",datetime.now(), start_time, 3) + self.route_supply_rails(self.vdd_name, 1) + print_time("Routing supply rails", datetime.now(), start_time, 3) start_time = datetime.now() self.route_simple_overlaps(vdd_name) self.route_simple_overlaps(gnd_name) - print_time("Simple overlap routing",datetime.now(), start_time, 3) + print_time("Simple overlap routing", datetime.now(), start_time, 3) # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() self.route_pins_to_rails(vdd_name) self.route_pins_to_rails(gnd_name) - print_time("Maze routing supplies",datetime.now(), start_time, 3) - #self.write_debug_gds("final.gds",False) + print_time("Maze routing supplies", datetime.now(), start_time, 3) + # self.write_debug_gds("final.gds", False) # Did we route everything?? if not self.check_all_routed(vdd_name): @@ -111,9 +104,8 @@ class supply_grid_router(router): return True - def check_all_routed(self, pin_name): - """ + """ Check that all pin groups are routed. """ for pg in self.pin_groups[pin_name]: @@ -125,7 +117,7 @@ class supply_grid_router(router): This checks for simple cases where a pin component already overlaps a supply rail. It will add an enclosure to ensure the overlap in wide DRC rule cases. """ - debug.info(1,"Routing simple overlap pins for {0}".format(pin_name)) + debug.info(1, "Routing simple overlap pins for {0}".format(pin_name)) # These are the wire tracks wire_tracks = self.supply_rail_tracks[pin_name] @@ -142,10 +134,10 @@ class supply_grid_router(router): continue # Else, if we overlap some of the space track, we can patch it with an enclosure - #pg.create_simple_overlap_enclosure(pg.grids) - #pg.add_enclosure(self.cell) + # pg.create_simple_overlap_enclosure(pg.grids) + # pg.add_enclosure(self.cell) - debug.info(1,"Routed {} simple overlap pins".format(routed_count)) + debug.info(1, "Routed {} simple overlap pins".format(routed_count)) def finalize_supply_rails(self, name): """ @@ -158,7 +150,7 @@ class supply_grid_router(router): connections = set() via_areas = [] - for i1,r1 in enumerate(all_rails): + for i1, r1 in enumerate(all_rails): # Only consider r1 horizontal rails e = next(iter(r1)) if e.z==1: @@ -166,9 +158,9 @@ class supply_grid_router(router): # We need to move this rail to the other layer for the z indices to match # during the intersection. This also makes a copy. - new_r1 = {vector3d(i.x,i.y,1) for i in r1} + new_r1 = {vector3d(i.x, i.y, 1) for i in r1} - for i2,r2 in enumerate(all_rails): + for i2, r2 in enumerate(all_rails): # Never compare to yourself if i1==i2: continue @@ -184,16 +176,16 @@ class supply_grid_router(router): # the overlap area for placement of a via overlap = new_r1 & r2 if len(overlap) >= 1: - debug.info(3,"Via overlap {0} {1}".format(len(overlap),overlap)) - connections.update([i1,i2]) + debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap)) + connections.update([i1, i2]) via_areas.append(overlap) # Go through and add the vias at the center of the intersection for area in via_areas: ll = grid_utils.get_lower_left(area) ur = grid_utils.get_upper_right(area) - center = (ll + ur).scale(0.5,0.5,0) - self.add_via(center,1) + center = (ll + ur).scale(0.5, 0.5, 0) + self.add_via(center, 1) # Determien which indices were not connected to anything above missing_indices = set([x for x in range(len(self.supply_rails[name]))]) @@ -204,13 +196,12 @@ class supply_grid_router(router): for rail_index in sorted(missing_indices, reverse=True): ll = grid_utils.get_lower_left(all_rails[rail_index]) ur = grid_utils.get_upper_right(all_rails[rail_index]) - debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur)) + debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur)) self.supply_rails[name].pop(rail_index) # Make the supply rails into a big giant set of grids for easy blockages. # Must be done after we determine which ones are connected. self.create_supply_track_set(name) - def add_supply_rails(self, name): """ @@ -223,7 +214,7 @@ class supply_grid_router(router): ur = grid_utils.get_upper_right(rail) z = ll.z pin = self.compute_pin_enclosure(ll, ur, z, name) - debug.info(3,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin)) + debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) self.cell.add_layout_pin(text=name, layer=pin.layer, offset=pin.ll(), @@ -243,19 +234,18 @@ class supply_grid_router(router): max_xoffset = self.rg.ur.x min_yoffset = self.rg.ll.y min_xoffset = self.rg.ll.x - # Horizontal supply rails start_offset = min_yoffset + supply_number for offset in range(start_offset, max_yoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(min_xoffset,offset,0)] + wave = [vector3d(min_xoffset, offset, 0)] # While we can keep expanding east in this horizontal track while wave and wave[0].x < max_xoffset: added_rail = self.find_supply_rail(name, wave, direction.EAST) if not added_rail: # Just seed with the next one - wave = [x+vector3d(1,0,0) for x in wave] + wave = [x+vector3d(1, 0, 0) for x in wave] else: # Seed with the neighbor of the end of the last rail wave = added_rail.neighbor(direction.EAST) @@ -264,15 +254,15 @@ class supply_grid_router(router): start_offset = min_xoffset + supply_number for offset in range(start_offset, max_xoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(offset,min_yoffset,1)] + wave = [vector3d(offset, min_yoffset, 1)] # While we can keep expanding north in this vertical track while wave and wave[0].y < max_yoffset: added_rail = self.find_supply_rail(name, wave, direction.NORTH) if not added_rail: # Just seed with the next one - wave = [x+vector3d(0,1,0) for x in wave] + wave = [x + vector3d(0, 1, 0) for x in wave] else: - # Seed with the neighbor of the end of the last rail + # Seed with the neighbor of the end of the last rail wave = added_rail.neighbor(direction.NORTH) def find_supply_rail(self, name, seed_wave, direct): @@ -294,7 +284,6 @@ class supply_grid_router(router): # Return the rail whether we approved it or not, # as it will be used to find the next start location return wave_path - def probe_supply_rail(self, name, start_wave, direct): """ @@ -328,23 +317,19 @@ class supply_grid_router(router): data structure. Return whether it was added or not. """ # We must have at least 2 tracks to drop plus 2 tracks for a via - if len(wave_path)>=4*self.rail_track_width: + if len(wave_path) >= 4 * self.rail_track_width: grid_set = wave_path.get_grids() self.supply_rails[name].append(grid_set) return True return False - - - - def route_supply_rails(self, name, supply_number): """ Route the horizontal and vertical supply rails across the entire design. Must be done with lower left at 0,0 """ - debug.info(1,"Routing supply rail {0}.".format(name)) + debug.info(1, "Routing supply rail {0}.".format(name)) # Compute the grid locations of the supply rails self.compute_supply_rails(name, supply_number) @@ -355,7 +340,6 @@ class supply_grid_router(router): # Add the rails themselves self.add_supply_rails(name) - def create_supply_track_set(self, pin_name): """ Make a single set of all the tracks for the rail and wire itself. @@ -364,24 +348,22 @@ class supply_grid_router(router): for rail in self.supply_rails[pin_name]: rail_set.update(rail) self.supply_rail_tracks[pin_name] = rail_set - - def route_pins_to_rails(self, pin_name): """ - This will route each of the remaining pin components to the supply rails. + This will route each of the remaining pin components to the supply rails. After it is done, the cells are added to the pin blockage list. """ remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) - debug.info(1,"Maze routing {0} with {1} pin components to connect.".format(pin_name, - remaining_components)) + debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name, + remaining_components)) - for index,pg in enumerate(self.pin_groups[pin_name]): + for index, pg in enumerate(self.pin_groups[pin_name]): if pg.is_routed(): continue - debug.info(3,"Routing component {0} {1}".format(pin_name, index)) + debug.info(3, "Routing component {0} {1}".format(pin_name, index)) # Clear everything in the routing grid. self.rg.reinit() @@ -400,28 +382,26 @@ class supply_grid_router(router): # Actually run the A* router if not self.run_router(detour_scale=5): - self.write_debug_gds("debug_route.gds",False) + self.write_debug_gds("debug_route.gds", False) - #if index==3 and pin_name=="vdd": - # self.write_debug_gds("route.gds",False) + # if index==3 and pin_name=="vdd": + # self.write_debug_gds("route.gds",False) - def add_supply_rail_target(self, pin_name): """ Add the supply rails of given name as a routing target. """ - debug.info(4,"Add supply rail target {}".format(pin_name)) + debug.info(4, "Add supply rail target {}".format(pin_name)) # Add the wire itself as the target self.rg.set_target(self.supply_rail_tracks[pin_name]) # But unblock all the rail tracks including the space - self.rg.set_blocked(self.supply_rail_tracks[pin_name],False) - + self.rg.set_blocked(self.supply_rail_tracks[pin_name], False) def set_supply_rail_blocked(self, value=True): """ Add the supply rails of given name as a routing target. """ - debug.info(4,"Blocking supply rail") + debug.info(4, "Blocking supply rail") for rail_name in self.supply_rail_tracks: self.rg.set_blocked(self.supply_rail_tracks[rail_name]) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 76e9f805..8dbcfaf4 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -59,14 +59,23 @@ class sram_1bank(sram_base): wmask_pos = [None] * len(self.all_ports) data_pos = [None] * len(self.all_ports) + # These positions utilize the channel route sizes. + # FIXME: Auto-compute these rather than manual computation. + # If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch. + # If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch. + # So, m3 non-pref pitch means that this is routed on the m2 layer. if self.write_size: - max_gap_size = self.m3_pitch * self.word_size + 2 * self.m1_pitch - max_gap_size_wmask = self.m2_pitch * max(self.num_wmasks + 1, self.col_addr_size + 1) + 2 * self.m1_pitch + self.data_bus_gap = self.m4_nonpref_pitch * 2 + self.data_bus_size = self.m4_nonpref_pitch * (self.word_size) + self.data_bus_gap + self.wmask_bus_gap = self.m2_nonpref_pitch * 2 + self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap else: - # This is M2 pitch even though it is on M1 to help stem via spacings on the trunk - # The M1 pitch is for supply rail spacings - max_gap_size = self.m2_pitch * max(self.word_size + 1,self.col_addr_size + 1) + 2 * self.m1_pitch + self.data_bus_gap = self.m3_nonpref_pitch * 2 + self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap + self.col_addr_bus_gap = self.m2_nonpref_pitch * 2 + self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap + # Port 0 port = 0 @@ -74,12 +83,12 @@ class sram_1bank(sram_base): if self.write_size: # Add the write mask flops below the write mask AND array. wmask_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size_wmask - self.dff.height) + - self.wmask_bus_size - self.dff.height) self.wmask_dff_insts[port].place(wmask_pos[port]) # Add the data flops below the write mask flops. data_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size - max_gap_size_wmask - 2 * self.dff.height) + - self.data_bus_size - self.wmask_bus_size - 2 * self.dff.height) self.data_dff_insts[port].place(data_pos[port]) else: # Add the data flops below the bank to the right of the lower-left of bank array @@ -89,7 +98,7 @@ class sram_1bank(sram_base): # sense amps. if port in self.write_ports: data_pos[port] = vector(self.bank.bank_array_ll.x, - -max_gap_size - self.dff.height) + -self.data_bus_size - self.dff.height) self.data_dff_insts[port].place(data_pos[port]) else: wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0) @@ -99,10 +108,10 @@ class sram_1bank(sram_base): if self.col_addr_dff: if self.write_size: col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -max_gap_size_wmask - self.col_addr_dff_insts[port].height) + -self.wmask_bus_size - self.col_addr_dff_insts[port].height) else: col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap, - -max_gap_size - self.col_addr_dff_insts[port].height) + -self.data_bus_size - self.col_addr_dff_insts[port].height) self.col_addr_dff_insts[port].place(col_addr_pos[port]) else: col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0) @@ -127,12 +136,12 @@ class sram_1bank(sram_base): if self.write_size: # Add the write mask flops below the write mask AND array. wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width, - self.bank.height + max_gap_size_wmask + self.dff.height) + self.bank.height + self.wmask_bus_size + self.dff.height) self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") # Add the data flops below the write mask flops data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + max_gap_size_wmask + max_gap_size + 2 * self.dff.height) + self.bank.height + self.wmask_bus_size + self.data_bus_size + 2 * self.dff.height) self.data_dff_insts[port].place(data_pos[port], mirror="MX") else: # Add the data flops above the bank to the left of the upper-right of bank array @@ -141,17 +150,17 @@ class sram_1bank(sram_base): # These flops go below the sensing and leave a gap to channel route to the # sense amps. data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, - self.bank.height + max_gap_size + self.dff.height) + self.bank.height + self.data_bus_size + self.dff.height) self.data_dff_insts[port].place(data_pos[port], mirror="MX") # Add the col address flops above the bank to the right of the upper-right of bank array if self.col_addr_dff: if self.write_size: col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + max_gap_size_wmask + self.dff.height) + self.bank.height + self.wmask_bus_size + self.dff.height) else: col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap, - self.bank.height + max_gap_size + self.dff.height) + self.bank.height + self.data_bus_size + self.dff.height) self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX") else: col_addr_pos[port] = self.bank_inst.ur() @@ -245,7 +254,7 @@ class sram_1bank(sram_base): # This uses a metal2 track to the right (for port0) of the control/row addr DFF # to route vertically. For port1, it is to the left. row_addr_clk_pin = self.row_addr_dff_insts[port].get_pin("clk") - if port%2: + if port % 2: control_clk_buf_pos = control_clk_buf_pin.lc() row_addr_clk_pos = row_addr_clk_pin.lc() mid1_pos = vector(self.row_addr_dff_insts[port].lx() - self.m2_pitch, @@ -258,19 +267,20 @@ class sram_1bank(sram_base): # This is the steiner point where the net branches out clk_steiner_pos = vector(mid1_pos.x, control_clk_buf_pos.y) - self.add_path("m1", [control_clk_buf_pos, clk_steiner_pos]) - self.add_via_center(layers=self.m1_stack, - offset=clk_steiner_pos) + self.add_path(control_clk_buf_pin.layer, [control_clk_buf_pos, clk_steiner_pos]) + self.add_via_stack_center(from_layer=control_clk_buf_pin.layer, + to_layer="m2", + offset=clk_steiner_pos) # Note, the via to the control logic is taken care of above - self.add_wire(("m3", "via2", "m2"), + self.add_wire(self.m2_stack[::-1], [row_addr_clk_pos, mid1_pos, clk_steiner_pos]) if self.col_addr_dff: dff_clk_pin = self.col_addr_dff_insts[port].get_pin("clk") dff_clk_pos = dff_clk_pin.center() mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y) - self.add_wire(("m3", "via2", "m2"), + self.add_wire(self.m2_stack[::-1], [dff_clk_pos, mid_pos, clk_steiner_pos]) if port in self.write_ports: @@ -282,7 +292,7 @@ class sram_1bank(sram_base): self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) - self.add_wire(("m3", "via2", "m2"), + self.add_wire(self.m2_stack[::-1], [data_dff_clk_pos, mid_pos, clk_steiner_pos]) if self.write_size: @@ -292,7 +302,7 @@ class sram_1bank(sram_base): # In some designs, the steiner via will be too close to the mid_pos via # so make the wire as wide as the contacts self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height)) - self.add_wire(("m3", "via2", "m2"), [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) + self.add_wire(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos]) def route_control_logic(self): """ Route the control logic pins that are not inputs """ @@ -304,14 +314,13 @@ class sram_1bank(sram_base): continue src_pin = self.control_logic_insts[port].get_pin(signal) dest_pin = self.bank_inst.get_pin(signal + "{}".format(port)) - self.connect_vbus_m2m3(src_pin, dest_pin) + self.connect_vbus(src_pin, dest_pin) for port in self.all_ports: # Only input (besides pins) is the replica bitline src_pin = self.control_logic_insts[port].get_pin("rbl_bl") dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) - - self.connect_hbus_m2m3(src_pin, dest_pin) + self.connect_hbus(src_pin, dest_pin) def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ @@ -324,33 +333,37 @@ class sram_1bank(sram_base): flop_pos = flop_pin.center() bank_pos = bank_pin.center() mid_pos = vector(bank_pos.x, flop_pos.y) - self.add_wire(("m3", "via2", "m2"), + self.add_wire(self.m2_stack[::-1], [flop_pos, mid_pos, bank_pos]) - self.add_via_center(layers=self.m2_stack, - offset=flop_pos) + self.add_via_stack_center(from_layer=flop_pin.layer, + to_layer="m3", + offset=flop_pos) def route_col_addr_dff(self): """ 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.col_addr_size + 2) * self.m1_pitch) + if port % 2: + offset = self.col_addr_dff_insts[port].ll() - vector(0, self.col_addr_bus_size) else: - offset = self.col_addr_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) + offset = self.col_addr_dff_insts[port].ul() + vector(0, self.col_addr_bus_gap) bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)] col_addr_bus_offsets = self.create_horizontal_bus(layer="m1", - pitch=self.m1_pitch, offset=offset, names=bus_names, length=self.col_addr_dff_insts[port].width) dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)] data_dff_map = zip(dff_names, bus_names) - self.connect_horizontal_bus(data_dff_map, self.col_addr_dff_insts[port], col_addr_bus_offsets) + self.connect_horizontal_bus(data_dff_map, + self.col_addr_dff_insts[port], + col_addr_bus_offsets) bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)] data_bank_map = zip(bank_names, bus_names) - self.connect_horizontal_bus(data_bank_map, self.bank_inst, col_addr_bus_offsets) + self.connect_horizontal_bus(data_bank_map, + self.bank_inst, + col_addr_bus_offsets) def route_data_dff(self): """ Connect the output of the data flops to the write driver """ @@ -358,48 +371,47 @@ class sram_1bank(sram_base): for port in self.write_ports: if self.write_size: if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m3_pitch) + offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size) else: - offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m3_pitch) + offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) else: if port % 2: - offset = self.data_dff_insts[port].ll() - vector(0, (self.word_size + 2) * self.m1_pitch) + offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size) else: - offset = self.data_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) + offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap) dff_names = ["dout_{}".format(x) for x in range(self.word_size)] dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names] if self.write_size: for x in dff_names: - pin_offset = self.data_dff_insts[port].get_pin(x).center() + pin = self.data_dff_insts[port].get_pin(x) + pin_offset = pin.center() self.add_via_center(layers=self.m1_stack, offset=pin_offset, directions=("V", "V")) - self.add_via_center(layers=self.m2_stack, - offset=pin_offset) - self.add_via_center(layers=self.m3_stack, - offset=pin_offset) + self.add_via_stack_center(from_layer="m2", + to_layer="m4", + offset=pin_offset) bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size)] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] if self.write_size: for x in bank_names: + pin = self.bank_inst.get_pin(x) if port % 2: - pin_offset = self.bank_inst.get_pin(x).uc() + pin_offset = pin.uc() else: - pin_offset = self.bank_inst.get_pin(x).bc() - self.add_via_center(layers=self.m1_stack, - offset=pin_offset) - self.add_via_center(layers=self.m2_stack, - offset=pin_offset) - self.add_via_center(layers=self.m3_stack, - offset=pin_offset) + pin_offset = pin.bc() + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m4", + offset=pin_offset) route_map = list(zip(bank_pins, dff_pins)) if self.write_size: layer_stack = self.m3_stack else: layer_stack = self.m1_stack + self.create_horizontal_channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack) @@ -409,9 +421,9 @@ class sram_1bank(sram_base): # This is where the channel will start (y-dimension at least) for port in self.write_ports: if port % 2: - offset = self.wmask_dff_insts[port].ll() - vector(0, (self.num_wmasks + 2) * self.m1_pitch) + offset = self.wmask_dff_insts[port].ll() - vector(0, self.wmask_bus_size) else: - offset = self.wmask_dff_insts[port].ul() + vector(0, 2 * self.m1_pitch) + offset = self.wmask_dff_insts[port].ul() + vector(0, self.wmask_bus_gap) dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names] diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index b9c4c909..bcb85308 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -7,6 +7,7 @@ # import datetime import debug +from math import log from importlib import reload from vector import vector from globals import OPTS, print_time @@ -137,7 +138,6 @@ class sram_base(design, verilog, lef): self.copy_power_pins(inst, "vdd") self.copy_power_pins(inst, "gnd") - import tech if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) return @@ -148,6 +148,7 @@ class sram_base(design, verilog, lef): grid_stack = power_grid except ImportError: # if no power_grid is specified by tech we use sensible defaults + import tech if "m4" in tech.layer: # Route a M3/M4 grid grid_stack = self.m3_stack @@ -496,70 +497,6 @@ class sram_base(design, verilog, lef): self.connect_inst(temp) return insts - - def connect_vbus_m2m3(self, src_pin, dest_pin): - """ - Helper routine to connect an instance to a vertical bus. - Routes horizontal then vertical L shape. - Dest pin is assumed to be on M2. - Src pin can be on M1/M2/M3. - """ - - if src_pin.cx()