From 1b6634bb9742ebc3be700e93d1fe6d50033f2d21 Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Wed, 29 Apr 2020 15:48:15 -0700 Subject: [PATCH 01/42] port data routing fix --- compiler/base/hierarchy_layout.py | 19 +++++++++++++++---- compiler/modules/port_data.py | 19 +++++++++---------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index e6f9ac9f..03d0116e 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -905,8 +905,11 @@ class layout(): max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) + max_x_lc = max([pin.lc().x for pin in pins]) + min_x_rc = min([pin.rc().x for pin in pins]) + # if we are less than a pitch, just create a non-preferred layer jog - if max_x-min_x <= pitch: + if max_x - min_x <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # Add the horizontal trunk on the vertical layer! @@ -927,7 +930,15 @@ class layout(): # Route each pin to the trunk for pin in pins: - mid = vector(pin.center().x, trunk_offset.y) + # If there is sufficient space, Route from the edge of the pins + # Otherwise, route from the center of the pins + if max_x_lc - min_x_rc > pitch: + if pin.center().x == max_x: + mid = vector(pin.lc().x, trunk_offset.y) + else: + mid = vector(pin.rc().x, trunk_offset.y) + else: + mid = vector(pin.center().x, trunk_offset.y) self.add_path(self.vertical_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) @@ -1014,7 +1025,7 @@ class layout(): def vcg_pin_overlap(pin1, pin2, vertical, pitch): """ Check for vertical or horizontal overlap of the two pins """ - + # FIXME: If the pins are not in a row, this may break. # However, a top pin shouldn't overlap another top pin, # for example, so the @@ -1095,7 +1106,7 @@ class layout(): # list of routes to do while vcg: # from pprint import pformat - # print("VCG:\n",pformat(vcg)) + # print("VCG:\n", pformat(vcg)) # get a route from conflict graph with empty fanout set net_name = None for net_name, conflicts in vcg.items(): diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index bdf10f39..641a1349 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -19,7 +19,7 @@ class port_data(design.design): """ def __init__(self, sram_config, port, name=""): - + sram_config.set_local_config(self) self.port = port if self.write_size is not None: @@ -444,7 +444,7 @@ class port_data(design.design): def route_sense_amp_out(self, port): """ Add pins for the sense amp output """ - + for bit in range(self.word_size): data_pin = self.sense_amp_array_inst.get_pin("data_{}".format(bit)) self.add_layout_pin_rect_center(text="dout_{0}".format(bit), @@ -521,10 +521,10 @@ class port_data(design.design): insn2_start_bit = 1 if self.port == 0 else 0 - self.connect_bitlines(inst1=inst1, - inst2=inst2, - num_bits=self.num_cols, - inst2_start_bit=insn2_start_bit) + self.channel_route_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.num_cols, + inst2_start_bit=insn2_start_bit) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -703,7 +703,7 @@ class port_data(design.design): bitline_dirs = ("H", "V") elif bottom_names[0].layer == "m1": bitline_dirs = ("V", "H") - + route_map = list(zip(bottom_names, top_names)) self.create_horizontal_channel_route(route_map, offset, self.m1_stack, bitline_dirs) @@ -717,7 +717,7 @@ class port_data(design.design): This assumes that they have sufficient space to create a jog in the middle between the two modules (if needed). """ - + bot_inst_group, top_inst_group = self._group_bitline_instances( inst1, inst2, num_bits, inst1_bls_template, inst1_start_bit, @@ -738,9 +738,8 @@ class port_data(design.design): vector(bot_br.x, yoffset), vector(top_br.x, yoffset), top_br]) - + def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" if self.precharge_array_inst: self.graph_inst_exclude.add(self.precharge_array_inst) - From 91dbbed9ba404bc951711d51f59023821e034508 Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Tue, 5 May 2020 12:18:26 -0700 Subject: [PATCH 02/42] added horizontal trunk route edit to vertical trunk route --- compiler/base/hierarchy_layout.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 03d0116e..4f74185b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -955,6 +955,9 @@ class layout(): max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) + max_y_uc = max([pin.uc().y for pin in pins]) + min_y_bc = min([pin.bc().y for pin in pins]) + # if we are less than a pitch, just create a non-preferred layer jog if max_y - min_y <= pitch: @@ -978,7 +981,15 @@ class layout(): # Route each pin to the trunk for pin in pins: - mid = vector(trunk_offset.x, pin.center().y) + # If there is sufficient space, Route from the edge of the pins + # Otherwise, route from the center of the pins + if max_y_uc - min_y_bc > pitch: + if pin.center().y == max_y: + mid = vector(trunk_offset.x, pin.bc().y) + else: + mid = vector(trunk_offset.x, pin.uc().y) + else: + mid = vector(trunk_offset.x, pin.center().y) self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) From e642b8521b01df689d9a55a5e6de4e72f4820d7a Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Wed, 6 May 2020 13:02:33 -0700 Subject: [PATCH 03/42] increase col_mux bitline spacing to fix cyclic vcg --- compiler/base/hierarchy_layout.py | 14 +++++++++----- compiler/pgates/single_level_column_mux.py | 15 +++++++++++++-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 4f74185b..ffb35151 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -905,11 +905,13 @@ class layout(): max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) + # max_x_lc & min_x_rc are for routing to/from the edge of the pins + # to increase spacing between contacts of different nets max_x_lc = max([pin.lc().x for pin in pins]) min_x_rc = min([pin.rc().x for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog - if max_x - min_x <= pitch: + if max_x_lc - min_x_rc <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # Add the horizontal trunk on the vertical layer! @@ -955,11 +957,13 @@ class layout(): max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) - max_y_uc = max([pin.uc().y for pin in pins]) - min_y_bc = min([pin.bc().y for pin in pins]) + # max_y_bc & min_y_uc are for routing to/from the edge of the pins + # to reduce spacing between contacts of different nets + max_y_bc = max([pin.bc().y for pin in pins]) + min_y_uc = min([pin.uc().y for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog - if max_y - min_y <= pitch: + if max_y_bc - min_y_uc <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] @@ -983,7 +987,7 @@ class layout(): for pin in pins: # If there is sufficient space, Route from the edge of the pins # Otherwise, route from the center of the pins - if max_y_uc - min_y_bc > pitch: + if max_y_bc - min_y_uc > pitch: if pin.center().y == max_y: mid = vector(trunk_offset.x, pin.bc().y) else: diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 9e38287f..0629a96c 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -11,7 +11,7 @@ from tech import drc, layer from vector import vector from sram_factory import factory import logical_effort - +from utils import round_to_grid class single_level_column_mux(pgate.pgate): """ @@ -75,6 +75,17 @@ class single_level_column_mux(pgate.pgate): bl_pos = vector(bl_pin.lx(), 0) br_pos = vector(br_pin.lx(), 0) + # The bitline input/output pins must be a least as wide as the metal pitch + # so that there is enough space to route to/from the pins. + # FIXME: bitline_metal_pitch should be greater than the horizontal metal pitch used in port_data + bitline_metal_pitch = self.width / 2 + bitline_width = br_pos.x - bl_pos.x + if bitline_width < bitline_metal_pitch: + bitline_width_increase_bl = round_to_grid((bitline_metal_pitch - bitline_width) / 2) + bitline_width_increase_br = round_to_grid((bitline_metal_pitch - bitline_width) - bitline_width_increase_bl) + bl_pos = bl_pos + vector(-bitline_width_increase_bl, 0) + br_pos = br_pos + vector( bitline_width_increase_br, 0) + # bl and br self.add_layout_pin(text="bl", layer=bl_pin.layer, @@ -133,7 +144,7 @@ class single_level_column_mux(pgate.pgate): def connect_bitlines(self): """ Connect the bitlines to the mux transistors """ - + # If li exists, use li and m1 for the mux, otherwise use m1 and m2 if "li" in layer: self.col_mux_stack = self.li_stack From dd73afc983951603788246d0bb578942f68409c2 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 23 Apr 2020 14:43:54 -0700 Subject: [PATCH 04/42] Changes to allow decoder height to be a 2x multiple of bitcell height. Convert to use li layer in pgates. Fix multifinger devices with li layers. Simplify wordline driver input routing. Fix power pin direction option update. PEP8 cleanup Changes to simplify metal preferred directions and pitches. Split of control logic tests. --- compiler/base/hierarchy_layout.py | 117 +++++++---- compiler/modules/bitcell_base_array.py | 9 +- compiler/modules/delay_chain.py | 2 +- compiler/modules/hierarchical_decoder.py | 75 ++++--- compiler/modules/hierarchical_predecode.py | 83 ++++---- compiler/modules/wordline_driver.py | 94 ++++----- compiler/pgates/pand2.py | 2 +- compiler/pgates/pand3.py | 2 +- compiler/pgates/pdriver.py | 2 +- compiler/pgates/pgate.py | 37 ++-- compiler/pgates/pinv.py | 39 ++-- compiler/pgates/pnand2.py | 114 +++++------ compiler/pgates/pnand3.py | 132 +++++++------ compiler/pgates/pnor2.py | 121 ++++++------ compiler/pgates/precharge.py | 2 +- compiler/pgates/ptx.py | 186 ++++++++++-------- compiler/router/supply_grid_router.py | 110 +++++------ compiler/tests/03_ptx_3finger_nmos_test.py | 3 +- compiler/tests/03_ptx_3finger_pmos_test.py | 3 +- compiler/tests/03_ptx_4finger_nmos_test.py | 5 +- compiler/tests/03_ptx_4finger_pmos_test.py | 5 +- compiler/tests/03_ptx_no_contacts_test.py | 60 ++++++ compiler/tests/04_pinv_100x_test.py | 36 ++++ compiler/tests/16_control_logic_r_test.py | 36 ++++ ...ic_test.py => 16_control_logic_rw_test.py} | 0 compiler/tests/16_control_logic_w_test.py | 35 ++++ 26 files changed, 770 insertions(+), 540 deletions(-) create mode 100755 compiler/tests/03_ptx_no_contacts_test.py create mode 100755 compiler/tests/04_pinv_100x_test.py create mode 100755 compiler/tests/16_control_logic_r_test.py rename compiler/tests/{16_control_logic_test.py => 16_control_logic_rw_test.py} (100%) create mode 100755 compiler/tests/16_control_logic_w_test.py diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index e6f9ac9f..a2761084 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -41,11 +41,18 @@ 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" + + if "li" in techlayer: + self.layer_indices = ["active", "li", "m1", "m2", "m3", "m4"] + else: + self.layer_indices = ["active", "m1", "m2", "m3", "m4"] + ############################################################ # GDS layout @@ -462,7 +469,12 @@ 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: + # Non-preferred directions + if directions == "nonpref": + directions = (self.get_preferred_direction(layers[2]), + self.get_preferred_direction(layers[0])) + # Preferred if not specified + elif not directions or directions == "pref": directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) @@ -487,7 +499,12 @@ class layout(): accounting for mirroring and rotation. """ - if not directions: + # Non-preferred directions + if directions == "nonpref": + directions = (self.get_preferred_direction(layers[2]), + self.get_preferred_direction(layers[0])) + # Preferred if not specified + elif not directions or directions == "pref": directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) @@ -513,36 +530,54 @@ 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 get_layer_index(self, layer_name): + """ + Return a layer index from bottom up in this tech. + """ + + return self.layer_indices.index(layer_name) + + 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 +586,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 = self.get_layer_index(from_layer) + to_id = self.get_layer_index(to_layer) if from_id < to_id: # grow the stack up search_id = 0 @@ -563,19 +598,36 @@ 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) + if from_layer == "active": + 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, + implant_type=implant_type, + well_type=well_type) + else: + 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 @@ -1200,27 +1252,18 @@ class layout(): "supply router." .format(name, inst.name, self.pwr_grid_layer)) - def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"): + def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): """ Add a single power pin from the lowest power_grid layer down to M1 (or li) at the given center location. The starting layer is specified to determine which vias are needed. """ - # Force vdd/gnd via stack to be vertically or horizontally oriented - # Default: None, uses prefered metal directions - if vertical: - direction = ("V", "V") - elif not vertical and vertical is not None: - direction = ("H", "H") - else: - direction = None - via = self.add_via_stack_center(from_layer=start_layer, to_layer=self.pwr_grid_layer, size=size, offset=loc, - direction=direction) + directions=directions) if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, 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/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/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 69822149..be3ba9c1 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -186,16 +186,25 @@ 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) - + # 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.m3_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.m3_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 + self.input_routing_width = (self.num_inputs + 1) * self.m3_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 @@ -217,8 +226,8 @@ class hierarchical_decoder(design.design): input_offset=vector(min_x - self.input_routing_width, 0) 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, + self.input_bus = self.create_vertical_pin_bus(layer="m3", + pitch=self.m3_pitch, offset=input_offset, names=input_bus_names, length=input_height) @@ -238,7 +247,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 +263,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 +274,15 @@ 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, + directions="nonpref") + self.add_via_stack_center(from_layer="m2", + to_layer="m3", + offset=output_offset, + directions="nonpref") + self.add_path(("m2"), [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -467,8 +480,8 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): # 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, + self.predecode_bus = self.create_vertical_pin_bus(layer="m3", + pitch=self.m3_pitch, offset=vector(0, 0), names=input_bus_names, length=self.height) @@ -538,7 +551,7 @@ class hierarchical_decoder(design.design): 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 + row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m2_pitch predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), @@ -546,11 +559,11 @@ class hierarchical_decoder(design.design): 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) + row_offset + self.m2_pitch) 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) + row_offset + 2 * self.m2_pitch) output_index = output_index + 1 def route_vdd_gnd(self): @@ -590,18 +603,26 @@ class hierarchical_decoder(design.design): # 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) + self.add_path("m2", [rail_pos, pin_pos]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m2", + offset=pin_pos, + directions=("H", "H")) + self.add_via_stack_center(from_layer="m2", + to_layer="m3", + offset=rail_pos, + directions="nonpref") # 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_wire(self.m2_stack, [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) + self.add_via_stack_center(from_layer=pin_pos.layer, + to_layer="m3", + offset=pin_pos, + directions="nonpref") def route_predecode_bus_inputs(self, rail_name, pin, x_offset): """ @@ -614,9 +635,11 @@ class hierarchical_decoder(design.design): 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) + self.add_path("m1", [pin_pos, mid_point1, mid_point2, rail_pos]) + self.add_via_stack_center(from_layer="m1", + to_layer="m3", + offset=rail_pos, + directions="nonpref") 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..c5fd1777 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -57,10 +57,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.m3_pitch # 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.m3_pitch # x offset to output inverters self.width = self.x_off_and + self.and_mod.width @@ -68,9 +68,9 @@ 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) - self.input_rails = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, + offset = vector(0.5 * self.m3_width, self.m1_pitch) + self.input_rails = self.create_vertical_pin_bus(layer="m3", + pitch=self.m3_pitch, offset=offset, names=input_names, length=self.height - 2 * self.m1_pitch) @@ -78,9 +78,9 @@ 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) - self.decode_rails = self.create_vertical_bus(layer="m2", - pitch=self.m2_pitch, + offset = vector(self.x_off_inv_1 + self.inv.width + 2 * self.m3_pitch, self.m1_pitch) + self.decode_rails = self.create_vertical_bus(layer="m3", + pitch=self.m3_pitch, offset=offset, names=decode_names, length=self.height - 2 * self.m1_pitch) @@ -146,15 +146,15 @@ 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, + self.add_path("m2", [in_pos, a_pos]) + self.add_via_center(layers=self.m2_stack, offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers=self.m1_stack, + self.add_via_center(layers=self.m2_stack, offset=[self.decode_rails[a_pin].x, y_offset]) def route_output_and(self): @@ -165,36 +165,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="m3", + offset=rail_pos, + directions="nonpref") - #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) - self.add_path("m1", [in_pos, inv_in_pos]) - self.add_via_center(layers=self.m1_stack, + # 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("m2", [in_pos, inv_in_pos]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m2", + offset=inv_in_pos, + directions="nonpref") + self.add_via_center(layers=self.m2_stack, offset=in_pos) def route_and_to_rails(self): @@ -205,17 +212,23 @@ 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_path("m2", [rail_pos, pin_pos]) + self.add_via_center(layers=self.m2_stack, + offset=rail_pos, + directions="nonpref") + self.add_via_stack_center(from_layer=pin.layer, + to_layer="m2", + offset=pin_pos, + directions=("H", "H")) def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver.py index 76a31074..3afe1c3c 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver.py @@ -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,32 @@ 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 - self.width = and2_xoffset + self.and2.width - self.height = self.and2.height * self.num_rows + self.width = and2_xoffset + self.decoders_per_row * self.and2.width + 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,12 +136,12 @@ 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 = and2_xoffset + 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 """ @@ -149,45 +154,30 @@ class wordline_driver(design.design): width=self.m2_width, height=self.height) - for row in range(self.rows): - and_inst = self.and_inst[row] + for inst_index in range(self.bitcell_rows): + and_inst = self.and_inst[inst_index] + row = math.floor(inst_index / self.decoders_per_row) + dec = inst_index % self.decoders_per_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) - - # connect the decoder input pin to and2 B 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 + b_pos = b_pin.center() + clk_offset = vector(en_pin.bc().x, b_pos.y) + self.add_segment_center(layer="m2", + start=clk_offset, + end=b_pos) + self.add_via_center(layers=self.m1_stack, + offset=b_pos) + # connect the decoder input pin to and2 A + a_pin = and_inst.get_pin("A") + a_pos = a_pin.center() + a_offset = vector(clk_offset.x, a_pos.y) # 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/pgates/pand2.py b/compiler/pgates/pand2.py index 4c044f1c..c042947f 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -76,7 +76,7 @@ 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): diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index f8cc2ac3..ec0a5a31 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -77,7 +77,7 @@ 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): diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 4bf654a4..6e79ae14 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -141,7 +141,7 @@ 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()]) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 918cd17b..7c294e0a 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -33,6 +33,14 @@ class pgate(design.design): # By default, we make it 10 M1 pitch tall self.height = 10*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)) + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -47,22 +55,21 @@ 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) + + self.add_rect(layer=source_pin.layer, offset=source_pin.ll(), height=height, width=source_pin.width()) @@ -346,4 +353,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..b522227b 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) @@ -202,16 +201,20 @@ class pinv(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", + add_source_contact="m1", + 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="m1", + 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): @@ -220,7 +223,7 @@ class pinv(pgate.pgate): 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), @@ -266,7 +269,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 +277,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..713f779d 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -72,32 +72,38 @@ 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="m1", + 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="m1", + 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="m1") + self.add_mod(self.pmos_right) + def setup_layout_constants(self): """ Pre-compute some handy layout parameters. """ @@ -111,11 +117,11 @@ class pnand2(pgate.pgate): # 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() + self.overlap_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 - extra_contact_space = max(-self.nmos_nd.get_pin("D").by(), 0) + extra_contact_space = max(-self.nmos_left.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) @@ -138,19 +144,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 +165,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,16 +200,17 @@ 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 + # This will help with the wells and the input/output placement + inputB_yoffset = self.pmos2_inst.by() - max(self.poly_extend_active + contact.poly_contact.height, + self.m1_space + 0.5 * contact.m1_via.height) self.route_input_gate(self.pmos2_inst, self.nmos2_inst, inputB_yoffset, "B", - position="center") + position="right") - # 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.inputA_yoffset = self.nmos2_inst.uy() + max(self.poly_extend_active + contact.poly_contact.height, + self.m1_space + 0.5 * contact.m1_via.height) self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, @@ -226,8 +227,7 @@ class pnand2(pgate.pgate): bottom_pin_offset = nmos_pin.center() # Output pin - c_pin = self.get_pin("B") - out_offset = vector(c_pin.cx() + self.m1_pitch, + out_offset = vector(nmos_pin.cx(), self.inputA_yoffset) # This routes on M2 @@ -251,27 +251,17 @@ 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) - 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]) + self.add_path(self.route_layer, + [top_pin_offset, mid1_offset, out_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..20337095 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -74,48 +74,59 @@ 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="m1", + 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="m1", + 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="m1") + 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="m1", + 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 @@ -143,27 +154,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 +183,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 +193,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 +206,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): @@ -250,14 +261,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, + out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch, self.inputA_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 +288,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..3ba7d9fd 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -71,31 +71,37 @@ 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="m1", + 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="m1") + 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="m1", + 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. """ @@ -108,12 +114,12 @@ class pnor2(pgate.pgate): # 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 @@ -121,7 +127,7 @@ class pnor2(pgate.pgate): # 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) + extra_contact_space = max(-self.nmos_right.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, @@ -146,19 +152,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): @@ -167,29 +173,26 @@ class pnor2(pgate.pgate): to provide maximum routing in channel """ - pmos1_pos = vector(self.pmos_ns.active_offset.x, - self.height - self.pmos_ns.active_height \ + 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 """ @@ -208,7 +211,7 @@ class pnor2(pgate.pgate): self.nmos2_inst, inputB_yoffset, "B", - position="center") + position="right") # This will help with the wells and the input/output placement self.inputA_yoffset = inputB_yoffset + self.input_spacing @@ -217,38 +220,32 @@ class pnor2(pgate.pgate): self.inputA_yoffset, "A") + self.output_yoffset = self.inputA_yoffset + self.input_spacing + 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 19aab991..b8ecb3e1 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -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"): diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index a5d9c3b5..6fcb722d 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -23,32 +23,44 @@ 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 not add_source_contact and "li" in layer: + add_source_contact = "li" + elif not add_source_contact: + add_source_contact = "m1" + + if not add_drain_contact and "li" in layer: + add_drain_contact = "li" + elif not add_drain_contact: + add_drain_contact = "m1" + # 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 +73,21 @@ 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 + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + 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 @@ -266,55 +286,44 @@ 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, layer, top): """ Connect each contact up/down to a source or drain pin """ + + if top: + dir = 1 + else: + dir = -1 + + if len(positions) <= 1: + return + + debug.check(layer != "active", "Must specify a metal for source connections.") + layer_space = getattr(self, "{}_space".format(layer)) + layer_width = getattr(self, "{}_width".format(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 - 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.scale(dir, dir) + # remove the individual connections + self.remove_layout_pin(pin_name) + # Add each vertical segment + for a in positions: + self.add_path(self.add_source_contact, + [a, a + pin_offset.scale(dir, dir)]) + # Add a single horizontal pin + self.add_layout_pin_segment_center(text=pin_name, + layer=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. @@ -433,12 +442,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", @@ -454,19 +463,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", self.add_source_contact, top=(self.tx_type=="pmos")) + + if self.connect_drain_active: + self.connect_fingered_active(drain_positions, "D", self.add_drain_contact, top=(self.tx_type=="nmos")) def get_stage_effort(self, cout): """Returns an object representing the parameters for delay in tau units.""" @@ -489,34 +501,36 @@ 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.") + + 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) + + if layer == "active": + source_via = getattr(contact, "{}_contact".format(layer)) + else: + source_via = getattr(contact, "{}_via".format(layer)) + # Source drain vias are all vertical + pin_height = source_via.first_layer_width + pin_width = source_via.first_layer_height 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/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/tests/03_ptx_3finger_nmos_test.py b/compiler/tests/03_ptx_3finger_nmos_test.py index 95700e8f..0b24ffd5 100755 --- a/compiler/tests/03_ptx_3finger_nmos_test.py +++ b/compiler/tests/03_ptx_3finger_nmos_test.py @@ -27,7 +27,8 @@ class ptx_3finger_nmos_test(openram_test): width=tech.drc["minwidth_tx"], mults=3, tx_type="nmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_3finger_pmos_test.py b/compiler/tests/03_ptx_3finger_pmos_test.py index 8c09cf9b..ccb8b586 100755 --- a/compiler/tests/03_ptx_3finger_pmos_test.py +++ b/compiler/tests/03_ptx_3finger_pmos_test.py @@ -27,7 +27,8 @@ class ptx_3finger_pmos_test(openram_test): width=tech.drc["minwidth_tx"], mults=3, tx_type="pmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_4finger_nmos_test.py b/compiler/tests/03_ptx_4finger_nmos_test.py index da9e438d..f7f3db78 100755 --- a/compiler/tests/03_ptx_4finger_nmos_test.py +++ b/compiler/tests/03_ptx_4finger_nmos_test.py @@ -23,11 +23,12 @@ class ptx_4finger_nmos_test(openram_test): import tech debug.info(2, "Checking three fingers NMOS") - fet = factory.create(module_type="ptx", + fet = factory.create(module_type="ptx", width= tech.drc["minwidth_tx"], mults=4, tx_type="nmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_4finger_pmos_test.py b/compiler/tests/03_ptx_4finger_pmos_test.py index 4fc60d5c..0bcdf909 100755 --- a/compiler/tests/03_ptx_4finger_pmos_test.py +++ b/compiler/tests/03_ptx_4finger_pmos_test.py @@ -23,11 +23,12 @@ class ptx_test(openram_test): import tech debug.info(2, "Checking three fingers PMOS") - fet = factory.create(module_type="ptx", + fet = factory.create(module_type="ptx", width=tech.drc["minwidth_tx"], mults=4, tx_type="pmos", - connect_active=True, + connect_source_active=True, + connect_drain_active=True, connect_poly=True) self.local_drc_check(fet) diff --git a/compiler/tests/03_ptx_no_contacts_test.py b/compiler/tests/03_ptx_no_contacts_test.py new file mode 100755 index 00000000..ff39da79 --- /dev/null +++ b/compiler/tests/03_ptx_no_contacts_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class ptx_no_contacts_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + import tech + + debug.info(2, "Checking single finger no source/drain") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=1, + add_source_contact=False, + add_drain_contact=False, + tx_type="nmos") + self.local_drc_check(fet) + + debug.info(2, "Checking multifinger no source/drain") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=4, + add_source_contact=False, + add_drain_contact=False, + tx_type="nmos") + self.local_drc_check(fet) + + debug.info(2, "Checking series ptx") + fet = factory.create(module_type="ptx", + width=tech.drc["minwidth_tx"], + mults=4, + series_devices=True, + tx_type="nmos") + self.local_drc_check(fet) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pinv_100x_test.py b/compiler/tests/04_pinv_100x_test.py new file mode 100755 index 00000000..91b55d5d --- /dev/null +++ b/compiler/tests/04_pinv_100x_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pinv_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(2, "Checking 100x inverter") + tx = factory.create(module_type="pinv", size=100) + self.local_check(tx) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/16_control_logic_r_test.py b/compiler/tests/16_control_logic_r_test.py new file mode 100755 index 00000000..b695f9c9 --- /dev/null +++ b/compiler/tests/16_control_logic_r_test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class control_logic_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(1, "Testing sample for control_logic_r") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="r") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_rw_test.py similarity index 100% rename from compiler/tests/16_control_logic_test.py rename to compiler/tests/16_control_logic_rw_test.py diff --git a/compiler/tests/16_control_logic_w_test.py b/compiler/tests/16_control_logic_w_test.py new file mode 100755 index 00000000..8407d1e8 --- /dev/null +++ b/compiler/tests/16_control_logic_w_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class control_logic_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(1, "Testing sample for control_logic_w") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32, port_type="w") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) From b7c66d7e073566d775ef8b9b902e8931a547e56d Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 7 May 2020 12:35:21 -0700 Subject: [PATCH 05/42] Changes to simplify metal preferred directions and pitches. Changes to allow decoder height to be a 2x multiple of bitcell height. Split of control logic tests. Fixed track spacing in SRAM and channel router PEP8 cleanup. --- compiler/base/contact.py | 10 +- compiler/base/design.py | 105 +++++-- compiler/base/hierarchy_layout.py | 297 ++++++++++-------- compiler/base/wire.py | 9 +- compiler/base/wire_path.py | 17 +- compiler/bitcells/bitcell.py | 2 +- compiler/bitcells/pbitcell.py | 53 ++-- compiler/modules/bank.py | 61 ++-- compiler/modules/bank_select.py | 31 +- compiler/modules/control_logic.py | 98 +++--- compiler/modules/dff_buf.py | 2 +- compiler/modules/hierarchical_decoder.py | 184 ++++++----- compiler/modules/hierarchical_predecode.py | 77 +++-- compiler/modules/port_address.py | 37 +-- compiler/modules/port_data.py | 2 +- compiler/modules/replica_bitcell_array.py | 22 +- compiler/modules/sense_amp_array.py | 4 +- compiler/modules/wordline_driver.py | 34 +- compiler/modules/write_driver_array.py | 2 +- compiler/modules/write_mask_and_array.py | 65 ++-- compiler/pgates/pand2.py | 17 +- compiler/pgates/pand3.py | 17 +- compiler/pgates/pbuf.py | 20 +- compiler/pgates/pdriver.py | 16 +- compiler/pgates/pgate.py | 69 ++-- compiler/pgates/pinv.py | 23 +- compiler/pgates/pnand2.py | 76 ++--- compiler/pgates/pnand3.py | 68 ++-- compiler/pgates/pnor2.py | 66 ++-- compiler/pgates/precharge.py | 2 +- compiler/pgates/ptristate_inv.py | 3 - compiler/pgates/ptx.py | 80 ++--- compiler/router/router_tech.py | 24 +- compiler/sram/sram_1bank.py | 120 +++---- compiler/sram/sram_base.py | 67 +--- compiler/tests/04_pinv_1x_test.py | 2 +- .../tests/06_hierarchical_decoder_test.py | 25 +- compiler/tests/16_control_logic_rw_test.py | 15 +- compiler/tests/19_single_bank_test.py | 40 +-- compiler/tests/21_ngspice_delay_test.py | 28 +- technology/freepdk45/tech/tech.py | 9 +- technology/scn4m_subm/tech/tech.py | 7 + 42 files changed, 953 insertions(+), 953 deletions(-) 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 a2761084..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 @@ -47,11 +48,7 @@ class layout(): self.pwr_grid_layer = power_grid[0] except ImportError: self.pwr_grid_layer = "m3" - - if "li" in techlayer: - self.layer_indices = ["active", "li", "m1", "m2", "m3", "m4"] - else: - self.layer_indices = ["active", "m1", "m2", "m3", "m4"] + ############################################################ @@ -451,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 @@ -459,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 """ @@ -468,16 +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. """ - - # Non-preferred directions - if directions == "nonpref": - directions = (self.get_preferred_direction(layers[2]), - self.get_preferred_direction(layers[0])) - # Preferred if not specified - elif not directions or directions == "pref": - 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, @@ -498,16 +519,6 @@ class layout(): Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """ - - # Non-preferred directions - if directions == "nonpref": - directions = (self.get_preferred_direction(layers[2]), - self.get_preferred_direction(layers[0])) - # Preferred if not specified - elif not directions or directions == "pref": - 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, @@ -547,13 +558,6 @@ class layout(): implant_type=implant_type, well_type=well_type) - def get_layer_index(self, layer_name): - """ - Return a layer index from bottom up in this tech. - """ - - return self.layer_indices.index(layer_name) - def add_via_stack_center(self, offset, from_layer, @@ -586,8 +590,8 @@ class layout(): if from_layer == to_layer: return last_via - from_id = self.get_layer_index(from_layer) - to_id = self.get_layer_index(to_layer) + from_id = layer_indices[from_layer] + to_id = layer_indices[to_layer] if from_id < to_id: # grow the stack up search_id = 0 @@ -608,24 +612,13 @@ class layout(): implant_type=implant_type, well_type=well_type) - if from_layer == "active": - 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, - implant_type=implant_type, - well_type=well_type) - else: - 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) + 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"): @@ -781,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. @@ -831,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, @@ -900,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 @@ -918,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 5e6ce004..393631e5 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -83,10 +83,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.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_*. @@ -128,16 +128,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, @@ -635,11 +633,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, @@ -650,11 +647,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, @@ -844,9 +840,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] @@ -854,7 +850,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): """ @@ -910,30 +908,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/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/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 be3ba9c1..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,6 +187,13 @@ 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): @@ -194,20 +202,21 @@ class hierarchical_decoder(design.design): else: nand_width = self.and3.width nand_inputs = 3 - self.internal_routing_width = self.m3_pitch * (self.total_number_of_predecoder_outputs + 1) + 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.m3_pitch - #print(self.decoders_per_row, nand_inputs) - #print(decoder_input_wire_height, self.cell_height) + 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.") + # 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 - self.input_routing_width = (self.num_inputs + 1) * self.m3_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 @@ -226,8 +235,7 @@ class hierarchical_decoder(design.design): input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] - self.input_bus = self.create_vertical_pin_bus(layer="m3", - pitch=self.m3_pitch, + self.input_bus = self.create_vertical_pin_bus(layer="m2", offset=input_offset, names=input_bus_names, length=input_height) @@ -276,13 +284,11 @@ class hierarchical_decoder(design.design): self.add_via_stack_center(from_layer="m2", to_layer="m3", - offset=input_offset, - directions="nonpref") + offset=input_offset) self.add_via_stack_center(from_layer="m2", to_layer="m3", - offset=output_offset, - directions="nonpref") - self.add_path(("m2"), [input_offset, output_offset]) + offset=output_offset) + self.add_path("m3", [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -424,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): """ @@ -460,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): """ @@ -480,8 +498,8 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): # 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="m3", - pitch=self.m3_pitch, + self.predecode_bus = self.create_vertical_pin_bus(layer="m2", + pitch=self.decoder_bus_pitch, offset=vector(0, 0), names=input_bus_names, length=self.height) @@ -525,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): @@ -549,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.m2_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.m2_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.m2_pitch) + output_index, + 2) output_index = output_index + 1 def route_vdd_gnd(self): @@ -583,47 +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("m2", [rail_pos, pin_pos]) - self.add_via_stack_center(from_layer=pin.layer, - to_layer="m2", - offset=pin_pos, - directions=("H", "H")) - self.add_via_stack_center(from_layer="m2", - to_layer="m3", - offset=rail_pos, - directions="nonpref") - # 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, [rail_pos, mid_pos, pin_pos]) - self.add_via_center(layers=self.m2_stack, - offset=rail_pos) - self.add_via_stack_center(from_layer=pin_pos.layer, - to_layer="m3", - offset=pin_pos, - directions="nonpref") + 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 @@ -631,15 +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_path("m1", [pin_pos, mid_point1, mid_point2, 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="m3", - offset=rail_pos, - directions="nonpref") + 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 c5fd1777..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.m3_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.m3_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.m3_width, self.m1_pitch) - self.input_rails = self.create_vertical_pin_bus(layer="m3", - pitch=self.m3_pitch, + offset = vector(0.5 * self.m2_width, self.m3_pitch) + self.input_rails = self.create_vertical_pin_bus(layer="m2", 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.m3_pitch, self.m1_pitch) - self.decode_rails = self.create_vertical_bus(layer="m3", - pitch=self.m3_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", offset=offset, names=decode_names, length=self.height - 2 * self.m1_pitch) @@ -151,11 +160,13 @@ class hierarchical_predecode(design.design): 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("m2", [in_pos, a_pos]) - self.add_via_center(layers=self.m2_stack, - offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_center(layers=self.m2_stack, - offset=[self.decode_rails[a_pin].x, y_offset]) + self.add_path("m1", [in_pos, a_pos]) + 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): """ @@ -188,21 +199,20 @@ class hierarchical_predecode(design.design): 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="m3", - offset=rail_pos, - directions="nonpref") + to_layer="m2", + offset=rail_pos) # 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("m2", [in_pos, inv_in_pos]) + self.add_path("m1", [in_pos, inv_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=inv_in_pos, - directions="nonpref") - self.add_via_center(layers=self.m2_stack, - offset=in_pos) + offset=in_pos) def route_and_to_rails(self): # This 2D array defines the connection mapping @@ -221,14 +231,19 @@ class hierarchical_predecode(design.design): 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("m2", [rail_pos, pin_pos]) - self.add_via_center(layers=self.m2_stack, - offset=rail_pos, - directions="nonpref") - self.add_via_stack_center(from_layer=pin.layer, + self.add_path("m1", [rail_pos, pin_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=("H", "H")) + directions=direction) def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ @@ -243,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 6938219a..a27894ae 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 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 = int(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 bdf10f39..91e20a45 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -694,7 +694,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 d5865658..c88dbe6d 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -354,32 +354,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] @@ -398,7 +396,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 c2f68f9b..cb413c45 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -130,13 +130,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 3afe1c3c..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 @@ -120,9 +120,11 @@ class wordline_driver(design.design): 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.decoders_per_row * self.and2.width + self.width = self.decoders_per_row * self.and2.width + well_gap self.height = self.and2.height * self.driver_rows for inst_index in range(self.bitcell_rows): @@ -136,7 +138,7 @@ class wordline_driver(design.design): y_offset = self.and2.height * row inst_mirror = "R0" - x_offset = and2_xoffset + dec * self.and2.width + x_offset = dec * self.and2.width and2_offset = [x_offset, y_offset] # add and2 @@ -147,32 +149,26 @@ class wordline_driver(design.design): """ 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 inst_index in range(self.bitcell_rows): and_inst = self.and_inst[inst_index] row = math.floor(inst_index / self.decoders_per_row) - dec = inst_index % self.decoders_per_row - - # en connection - b_pin = and_inst.get_pin("B") - b_pos = b_pin.center() - clk_offset = vector(en_pin.bc().x, b_pos.y) - self.add_segment_center(layer="m2", - start=clk_offset, - end=b_pos) - self.add_via_center(layers=self.m1_stack, - offset=b_pos) + # Drop a via + b_pin = and_inst.get_pin("B") + 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() - a_offset = vector(clk_offset.x, a_pos.y) # must under the clk line in M1 self.add_layout_pin_segment_center(text="in_{0}".format(row), layer="m1", diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index ab369dda..49e830e2 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -165,7 +165,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 c042947f..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() @@ -80,22 +81,6 @@ class pand2(pgate.pgate): [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 ec0a5a31..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() @@ -81,22 +82,6 @@ class pand3(pgate.pgate): [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 6e79ae14..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): @@ -146,21 +147,6 @@ class pdriver(pgate.pgate): 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 7c294e0a..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,8 +31,8 @@ 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" @@ -40,7 +41,15 @@ class pgate(design.design): 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() @@ -68,13 +77,14 @@ class pgate(design.design): 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 @@ -100,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 \ @@ -118,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) \ @@ -201,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) @@ -256,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) @@ -286,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) """ diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index b522227b..e0e1ce81 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -107,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, @@ -201,7 +194,7 @@ class pinv(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer, connect_poly=True, connect_drain_active=True) @@ -211,24 +204,12 @@ class pinv(pgate.pgate): width=self.pmos_width, mults=self.tx_mults, tx_type="pmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer, connect_poly=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. diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 713f779d..5d20201e 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -76,7 +76,7 @@ class pnand2(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact="active") self.add_mod(self.nmos_left) @@ -92,7 +92,7 @@ class pnand2(pgate.pgate): width=self.pmos_width, mults=self.tx_mults, tx_type="pmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer) self.add_mod(self.pmos_left) @@ -101,43 +101,17 @@ class pnand2(pgate.pgate): mults=self.tx_mults, tx_type="pmos", add_source_contact=self.route_layer, - add_drain_contact="m1") + 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_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 - extra_contact_space = max(-self.nmos_left.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 netlist. @@ -200,35 +174,46 @@ class pnand2(pgate.pgate): def route_inputs(self): """ Route the A and B inputs """ + + + # 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 - inputB_yoffset = self.pmos2_inst.by() - max(self.poly_extend_active + contact.poly_contact.height, - self.m1_space + 0.5 * contact.m1_via.height) self.route_input_gate(self.pmos2_inst, self.nmos2_inst, - inputB_yoffset, + self.inputB_yoffset, "B", - position="right") + position="center") - self.inputA_yoffset = self.nmos2_inst.uy() + max(self.poly_extend_active + contact.poly_contact.height, - self.m1_space + 0.5 * contact.m1_via.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 - out_offset = vector(nmos_pin.cx(), - 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 @@ -253,10 +238,15 @@ class pnand2(pgate.pgate): # 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) - + top_mid_offset = vector(top_pin_offset.x, out_offset.y) + # Top transistors self.add_path(self.route_layer, - [top_pin_offset, mid1_offset, out_offset, bottom_pin_offset]) + [top_pin_offset, top_mid_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", diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 20337095..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. @@ -94,7 +94,7 @@ class pnand3(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact="active") self.add_mod(self.nmos_left) @@ -102,7 +102,7 @@ class pnand3(pgate.pgate): width=self.pmos_width, mults=self.tx_mults, tx_type="pmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer) self.add_mod(self.pmos_left) @@ -111,14 +111,14 @@ class pnand3(pgate.pgate): mults=self.tx_mults, tx_type="pmos", add_source_contact=self.route_layer, - add_drain_contact="m1") + 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="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer) self.add_mod(self.pmos_right) @@ -132,21 +132,6 @@ class pnand3(pgate.pgate): # 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): """ @@ -223,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 @@ -262,7 +244,7 @@ class pnand3(pgate.pgate): nmos3_pin = self.nmos3_inst.get_pin("D") out_offset = vector(nmos3_pin.cx() + self.route_layer_pitch, - self.inputA_yoffset) + self.output_yoffset) # Go up to metal2 for ease on all output pins # self.add_via_center(layers=self.m1_stack, diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 3ba7d9fd..f522578e 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -75,7 +75,7 @@ class pnor2(pgate.pgate): width=self.nmos_width, mults=self.tx_mults, tx_type="nmos", - add_source_contact="m1", + add_source_contact=self.route_layer, add_drain_contact=self.route_layer) self.add_mod(self.nmos_left) @@ -84,14 +84,14 @@ class pnor2(pgate.pgate): mults=self.tx_mults, tx_type="nmos", add_source_contact=self.route_layer, - add_drain_contact="m1") + 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="m1", + add_source_contact=self.route_layer, add_drain_contact="active") self.add_mod(self.pmos_left) @@ -106,12 +106,6 @@ class pnor2(pgate.pgate): 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_right.get_pin("D").center() - self.pmos_left.get_pin("S").center() @@ -124,27 +118,7 @@ class pnor2(pgate.pgate): + 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_right.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 @@ -172,10 +146,19 @@ 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_right.active_offset.x, - self.height - self.pmos_right.active_height \ - - self.top_bottom_space) + 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 @@ -186,7 +169,6 @@ class pnor2(pgate.pgate): self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -205,22 +187,28 @@ 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="right") + 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.input_spacing + self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch def route_output(self): """ Route the Z output """ diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index b8ecb3e1..d47e445f 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -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 6fcb722d..c584e70b 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -38,15 +38,18 @@ class ptx(design.design): connect_poly=False, num_contacts=None): - if not add_source_contact and "li" in layer: - add_source_contact = "li" - elif not add_source_contact: - add_source_contact = "m1" + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" - if not add_drain_contact and "li" in layer: - add_drain_contact = "li" - elif not add_drain_contact: - add_drain_contact = "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 @@ -81,10 +84,6 @@ class ptx(design.design): self.series_devices = series_devices self.num_contacts = num_contacts - if "li" in layer: - self.route_layer = "li" - else: - self.route_layer = "m1" self.route_layer_width = drc("minwidth_{}".format(self.route_layer)) self.route_layer_space = drc("{0}_to_{0}".format(self.route_layer)) @@ -286,23 +285,16 @@ class ptx(design.design): width=poly_width, height=self.poly_width) - def connect_fingered_active(self, positions, pin_name, layer, top): + def connect_fingered_active(self, positions, pin_name, top): """ Connect each contact up/down to a source or drain pin """ - - if top: - dir = 1 - else: - dir = -1 if len(positions) <= 1: return - debug.check(layer != "active", "Must specify a metal for source connections.") - - layer_space = getattr(self, "{}_space".format(layer)) - layer_width = getattr(self, "{}_width".format(layer)) + 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 @@ -311,16 +303,21 @@ class ptx(design.design): # This is the width of a m1 extend the ends of the pin end_offset = vector(layer_width / 2.0, 0) - offset = pin_offset.scale(dir, dir) + # We move the opposite direction from the bottom + if not top: + offset = pin_offset.scale(-1, -1) + else: + 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.add_source_contact, - [a, a + pin_offset.scale(dir, dir)]) + self.add_path(self.route_layer, + [a, a + offset]) # Add a single horizontal pin self.add_layout_pin_segment_center(text=pin_name, - layer=layer, + layer=self.route_layer, start=positions[0] + offset - end_offset, end=positions[-1] + offset + end_offset) @@ -475,10 +472,10 @@ class ptx(design.design): offset=pos) if self.connect_source_active: - self.connect_fingered_active(source_positions, "S", self.add_source_contact, top=(self.tx_type=="pmos")) + self.connect_fingered_active(source_positions, "S", top=(self.tx_type=="pmos")) if self.connect_drain_active: - self.connect_fingered_active(drain_positions, "D", self.add_drain_contact, top=(self.tx_type=="nmos")) + 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.""" @@ -509,21 +506,24 @@ class ptx(design.design): else: debug.error("Invalid source drain name.") - 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) + 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) - if layer == "active": - source_via = getattr(contact, "{}_contact".format(layer)) + pin_height = via.mod.second_layer_height + pin_width = via.mod.second_layer_width else: - source_via = getattr(contact, "{}_via".format(layer)) + via = None + + pin_height = None + pin_width = None + # Source drain vias are all vertical - pin_height = source_via.first_layer_width - pin_width = source_via.first_layer_height self.add_layout_pin_rect_center(text=label, layer=layer, offset=pos, 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/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() Date: Mon, 11 May 2020 16:22:08 -0700 Subject: [PATCH 06/42] Add no well option. Add stack gates vertical option. --- compiler/pgates/pand2.py | 80 +++++++++++++++++++++++++------- compiler/pgates/pand3.py | 76 ++++++++++++++++++++++++------ compiler/pgates/pbuf.py | 3 +- compiler/pgates/pdriver.py | 5 +- compiler/pgates/pgate.py | 18 +++++-- compiler/pgates/pinv.py | 7 +-- compiler/pgates/pnand2.py | 9 ++-- compiler/pgates/pnand3.py | 7 +-- compiler/pgates/pnor2.py | 11 +++-- compiler/tests/04_pand2_test.py | 4 ++ compiler/tests/04_pand3_test.py | 4 ++ compiler/tests/04_pnand2_test.py | 5 ++ compiler/tests/04_pnand3_test.py | 5 ++ compiler/tests/04_pnor2_test.py | 4 ++ 14 files changed, 187 insertions(+), 51 deletions(-) diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 69af1e62..6df877fd 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -15,14 +15,14 @@ class pand2(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None): - debug.info(1, "Creating pnand2 {}".format(name)) + def __init__(self, name, size=1, height=None, vertical=False): + debug.info(1, "Creating pand2 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size - - # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + + pgate.pgate.__init__(self, name) def create_netlist(self): self.add_pins() @@ -30,17 +30,22 @@ class pand2(pgate.pgate): self.create_insts() def create_modules(self): - self.nand = factory.create(module_type="pnand2", height=self.height) + self.nand = factory.create(module_type="pnand2", + height=self.height) self.add_mod(self.nand) self.inv = factory.create(module_type="pdriver", - neg_polarity=True, - fanout=self.size, + size_list=[self.size], height=self.height) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() @@ -68,17 +73,60 @@ class pand2(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") 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(self.route_layer, - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) def add_layout_pins(self): pin = self.inv_inst.get_pin("Z") diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index 841ac69d..5e2e83f8 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -15,10 +15,11 @@ class pand3(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, vertical=False): debug.info(1, "Creating pand3 {}".format(name)) self.add_comment("size: {}".format(size)) - + + self.vertical = vertical self.size = size # Creates the netlist and layout @@ -31,16 +32,22 @@ class pand3(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand3", height=self.height) + self.nand = factory.create(module_type="pnand3", + height=self.height) self.add_mod(self.nand) - self.inv = factory.create(module_type="pinv", - size=self.size, + self.inv = factory.create(module_type="pdriver", + size_list=[self.size], height=self.height) self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if self.vertical: + self.height = 2 * self.nand.height + self.width = max(self.nand.width, self.inv.width) + else: + self.width = self.nand.width + self.inv.width + self.place_insts() self.add_wires() self.add_layout_pins() @@ -69,18 +76,61 @@ class pand3(pgate.pgate): # Add NAND to the right self.nand_inst.place(offset=vector(0, 0)) - # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + if self.vertical: + # Add INV above + self.inv_inst.place(offset=vector(self.inv.width, + 2 * self.nand.height), + mirror="XY") + else: + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + # Second gnd of the inverter gate + if self.vertical: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, self.height), + width=self.width) + if self.vertical: + # Shared between two gates + y_offset = 0.5 * self.height + else: + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") 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(z1_pin.layer, - [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) - + if self.vertical: + route_layer = "m2" + self.add_via_stack_center(offset=z1_pin.center(), + from_layer=z1_pin.layer, + to_layer=route_layer) + self.add_zjog(route_layer, + z1_pin.uc(), + a2_pin.bc(), + "V") + self.add_via_stack_center(offset=a2_pin.center(), + from_layer=a2_pin.layer, + to_layer=route_layer) + else: + route_layer = self.route_layer + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + def add_layout_pins(self): pin = self.inv_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Z", diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index 4d90286d..8b9c4eab 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -56,7 +56,8 @@ class pbuf(pgate.pgate): self.inv2 = factory.create(module_type="pinv", size=self.size, - height=self.height) + height=self.height, + add_wells=False) self.add_mod(self.inv2) def create_insts(self): diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 9bba7aee..b36d3e95 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -87,10 +87,13 @@ class pdriver(pgate.pgate): def add_modules(self): self.inv_list = [] + add_well = True for size in self.size_list: temp_inv = factory.create(module_type="pinv", size=size, - height=self.height) + height=self.height, + add_wells=add_well) + add_well=False self.inv_list.append(temp_inv) self.add_mod(temp_inv) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 5351be07..be317f48 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -24,7 +24,7 @@ class pgate(design.design): functions for parameterized gates. """ - def __init__(self, name, height=None): + def __init__(self, name, height=None, add_wells=True): """ Creates a generic cell """ design.design.__init__(self, name) @@ -33,7 +33,8 @@ class pgate(design.design): elif not height: # By default, something simple self.height = 14 * self.m1_pitch - + self.add_wells = add_wells + if "li" in layer: self.route_layer = "li" else: @@ -150,7 +151,7 @@ class pgate(design.design): # This should match the cells in the cell library self.nwell_y_offset = 0.48 * self.height - full_height = self.height + 0.5* self.m1_width + full_height = self.height + 0.5 * self.m1_width # FIXME: float rounding problem if "nwell" in layer: @@ -302,10 +303,17 @@ class pgate(design.design): def determine_width(self): """ Determine the width based on the well contacts (assumed to be on the right side) """ + # Width is determined by well contact and spacing and allowing a supply via between each cell - self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width + if self.add_wells: + self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width + # Height is an input parameter, so it is not recomputed. + else: + max_active_xoffset = self.find_highest_layer_coords("active").x + max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space + self.width = max(max_active_xoffset, max_route_xoffset) + self.well_width = self.width + 2 * self.nwell_enclose_active - # Height is an input parameter, so it is not recomputed. @staticmethod def bin_width(tx_type, target_width): diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index e0e1ce81..214e66dc 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -32,7 +32,7 @@ class pinv(pgate.pgate): from center of rail to rail. """ - def __init__(self, name, size=1, beta=parameter["beta"], height=None): + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): debug.info(2, "creating pinv structure {0} with size of {1}".format(name, @@ -44,7 +44,7 @@ class pinv(pgate.pgate): self.pmos_size = beta * size self.beta = beta - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): """ Calls all functions related to the generation of the netlist """ @@ -56,7 +56,8 @@ class pinv(pgate.pgate): def create_layout(self): """ Calls all functions related to the generation of the layout """ self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() self.determine_width() self.extend_wells() self.route_supply_rails() diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 5d20201e..59995133 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -20,7 +20,7 @@ class pnand2(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nand """ debug.info(2, @@ -43,7 +43,7 @@ class pnand2(pgate.pgate): (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -55,13 +55,14 @@ class pnand2(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() self.route_inputs() - self.route_output() self.add_boundary() def add_pins(self): diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 454b75dd..446b8212 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -19,7 +19,7 @@ class pnand3(pgate.pgate): This module generates gds of a parametrically sized 2-input nand. This model use ptx to generate a 2-input nand within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 3 input nand """ debug.info(2, @@ -45,7 +45,7 @@ class pnand3(pgate.pgate): (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def add_pins(self): """ Adds pins for spice netlist """ @@ -63,7 +63,8 @@ class pnand3(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() self.determine_width() self.route_supply_rails() self.connect_rails() diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index f522578e..2126e86c 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -19,7 +19,7 @@ class pnor2(pgate.pgate): This module generates gds of a parametrically sized 2-input nor. This model use ptx to generate a 2-input nor within a cetrain height. """ - def __init__(self, name, size=1, height=None): + def __init__(self, name, size=1, height=None, add_wells=True): """ Creates a cell for a simple 2 input nor """ debug.info(2, @@ -42,7 +42,7 @@ class pnor2(pgate.pgate): (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -54,13 +54,14 @@ class pnor2(pgate.pgate): self.setup_layout_constants() self.place_ptx() - self.add_well_contacts() + if self.add_wells: + self.add_well_contacts() + self.route_inputs() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() - self.route_inputs() - self.route_output() self.add_boundary() def add_pins(self): diff --git a/compiler/tests/04_pand2_test.py b/compiler/tests/04_pand2_test.py index f7e5f304..21a0a38e 100755 --- a/compiler/tests/04_pand2_test.py +++ b/compiler/tests/04_pand2_test.py @@ -29,6 +29,10 @@ class pand2_test(openram_test): a = pand2.pand2(name="pand2x4", size=4) self.local_check(a) + debug.info(2, "Testing vertical pand2 gate 4x") + a = pand2.pand2(name="pand2x4", size=4, vertical=True) + self.local_check(a) + globals.end_openram() # instantiate a copdsay of the class to actually run the test diff --git a/compiler/tests/04_pand3_test.py b/compiler/tests/04_pand3_test.py index e58f1ee9..f851077b 100755 --- a/compiler/tests/04_pand3_test.py +++ b/compiler/tests/04_pand3_test.py @@ -29,6 +29,10 @@ class pand3_test(openram_test): a = pand3.pand3(name="pand3x4", size=4) self.local_check(a) + debug.info(2, "Testing vertical pand3 gate 4x") + a = pand3.pand3(name="pand3x4", size=4, vertical=True) + self.local_check(a) + globals.end_openram() # instantiate a copdsay of the class to actually run the test diff --git a/compiler/tests/04_pnand2_test.py b/compiler/tests/04_pnand2_test.py index f939738a..77a6932c 100755 --- a/compiler/tests/04_pnand2_test.py +++ b/compiler/tests/04_pnand2_test.py @@ -25,6 +25,11 @@ class pnand2_test(openram_test): tx = factory.create(module_type="pnand2", size=1) self.local_check(tx) + debug.info(2, "Checking 2-input nand gate") + tx = factory.create(module_type="pnand2", size=1, add_wells=False) + # Only DRC because well contacts will fail LVS + self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_pnand3_test.py b/compiler/tests/04_pnand3_test.py index f1af19ac..82bf1846 100755 --- a/compiler/tests/04_pnand3_test.py +++ b/compiler/tests/04_pnand3_test.py @@ -25,6 +25,11 @@ class pnand3_test(openram_test): tx = factory.create(module_type="pnand3", size=1) self.local_check(tx) + debug.info(2, "Checking 3-input nand gate") + tx = factory.create(module_type="pnand3", size=1, add_wells=False) + # Only DRC because well contacts will fail LVS + self.local_drc_check(tx) + globals.end_openram() diff --git a/compiler/tests/04_pnor2_test.py b/compiler/tests/04_pnor2_test.py index ea0d6dbc..1e8abaa4 100755 --- a/compiler/tests/04_pnor2_test.py +++ b/compiler/tests/04_pnor2_test.py @@ -25,6 +25,10 @@ class pnor2_test(openram_test): tx = factory.create(module_type="pnor2", size=1) self.local_check(tx) + debug.info(2, "Checking 2-input nor gate") + tx = factory.create(module_type="pnor2", size=1, add_wells=False) + self.local_drc_check(tx) + globals.end_openram() # run the test from the command line From 848241a3ad501ef78df9ac05a99e3ba4ef411d67 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 11 May 2020 16:22:17 -0700 Subject: [PATCH 07/42] PEP8 cleanup --- compiler/base/geometry.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index cfcfa73a..96da3d99 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -66,14 +66,17 @@ class geometry: self.compute_boundary(self.offset, self.mirror, self.rotate) def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0): - """ Transform with offset, mirror and rotation to get the absolute pin location. - We must then re-find the ll and ur. The master is the cell instance. """ + """ + Transform with offset, mirror and rotation to get the absolute pin location. + We must then re-find the ll and ur. The master is the cell instance. + """ if OPTS.netlist_only: - self.boundary = [vector(0,0), vector(0,0)] + self.boundary = [vector(0, 0), vector(0, 0)] return (ll, ur) = [vector(0, 0), vector(self.width, self.height)] + # Mirroring is performed before rotation if mirror == "MX": ll = ll.scale(1, -1) ur = ur.scale(1, -1) @@ -195,14 +198,13 @@ class instance(geometry): blockages = [] blockages = self.mod.gds.getBlockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) else: blockages = self.mod.get_blockages(lpp) for b in blockages: - new_blockages.append(self.transform_coords(b,self.offset, mirr, angle)) + new_blockages.append(self.transform_coords(b, self.offset, mirr, angle)) return new_blockages - def gds_write_file(self, new_layout): """Recursively writes all the sub-modules in this instance""" debug.info(4, "writing instance: " + self.name) @@ -225,26 +227,25 @@ class instance(geometry): self.update_boundary() debug.info(3, "placing instance {}".format(self)) - - def get_pin(self,name,index=-1): + def get_pin(self, name, index=-1): """ Return an absolute pin that is offset and transformed based on this instance location. Index will return one of several pins.""" import copy if index == -1: pin = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pin.transform(self.offset, self.mirror, self.rotate) return pin else: pins = copy.deepcopy(self.mod.get_pin(name)) - pin.transform(self.offset,self.mirror,self.rotate) + pins.transform(self.offset, self.mirror, self.rotate) return pin[index] def get_num_pins(self, name): """ Return the number of pins of a given name """ return len(self.mod.get_pins(name)) - def get_pins(self,name): + def get_pins(self, name): """ Return an absolute pin that is offset and transformed based on this instance location. """ @@ -253,7 +254,7 @@ class instance(geometry): new_pins = [] for p in pin: - p.transform(self.offset,self.mirror,self.rotate) + p.transform(self.offset, self.mirror, self.rotate) new_pins.append(p) return new_pins @@ -265,6 +266,7 @@ class instance(geometry): """ override print function output """ return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")" + class path(geometry): """Represents a Path""" @@ -322,7 +324,7 @@ class label(geometry): self.size = 0 - debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) + debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset)) def gds_write_file(self, new_layout): """Writes the text label to GDS""" @@ -340,7 +342,7 @@ class label(geometry): def __str__(self): """ override print function output """ - return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) def __repr__(self): """ override print function output """ From 617bf302d18f1863da212073d8e80d71097d7dae Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 May 2020 14:46:42 -0700 Subject: [PATCH 08/42] Add option to remove wells. Save area in pgates with redundant wells. --- compiler/pgates/pand2.py | 13 ++++++++----- compiler/pgates/pand3.py | 15 ++++++++++----- compiler/pgates/pdriver.py | 8 ++++---- compiler/pgates/pgate.py | 17 +++++++++-------- compiler/pgates/pinv.py | 4 ++-- compiler/pgates/pnand2.py | 1 - compiler/pgates/single_level_column_mux.py | 4 ++-- compiler/tests/04_pnor2_test.py | 4 ---- .../04_single_level_column_mux_pbitcell_test.py | 0 compiler/tests/05_replica_bitcell_array_test.py | 0 ...ngle_level_column_mux_array_pbitcell_test.py | 0 .../tests/08_wordline_driver_pbitcell_test.py | 0 .../tests/09_sense_amp_array_test_pbitcell.py | 0 .../10_write_driver_array_pbitcell_test.py | 0 ...10_write_driver_array_wmask_pbitcell_test.py | 0 .../10_write_mask_and_array_pbitcell_test.py | 0 compiler/tests/19_bank_select_pbitcell_test.py | 0 17 files changed, 35 insertions(+), 31 deletions(-) mode change 100644 => 100755 compiler/tests/04_single_level_column_mux_pbitcell_test.py mode change 100644 => 100755 compiler/tests/05_replica_bitcell_array_test.py mode change 100644 => 100755 compiler/tests/07_single_level_column_mux_array_pbitcell_test.py mode change 100644 => 100755 compiler/tests/08_wordline_driver_pbitcell_test.py mode change 100644 => 100755 compiler/tests/09_sense_amp_array_test_pbitcell.py mode change 100644 => 100755 compiler/tests/10_write_driver_array_pbitcell_test.py mode change 100644 => 100755 compiler/tests/10_write_driver_array_wmask_pbitcell_test.py mode change 100644 => 100755 compiler/tests/10_write_mask_and_array_pbitcell_test.py mode change 100644 => 100755 compiler/tests/19_bank_select_pbitcell_test.py diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 6df877fd..dfbdc3e7 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -15,14 +15,14 @@ class pand2(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None, vertical=False): + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): debug.info(1, "Creating pand2 {}".format(name)) self.add_comment("size: {}".format(size)) self.vertical = vertical self.size = size - pgate.pgate.__init__(self, name) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -31,12 +31,15 @@ class pand2(pgate.pgate): def create_modules(self): self.nand = factory.create(module_type="pnand2", - height=self.height) - self.add_mod(self.nand) + height=self.height, + add_wells=False) self.inv = factory.create(module_type="pdriver", size_list=[self.size], - height=self.height) + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index 5e2e83f8..d7f0b00e 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -15,7 +15,7 @@ class pand3(pgate.pgate): """ This is a simple buffer used for driving loads. """ - def __init__(self, name, size=1, height=None, vertical=False): + def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): debug.info(1, "Creating pand3 {}".format(name)) self.add_comment("size: {}".format(size)) @@ -23,7 +23,7 @@ class pand3(pgate.pgate): self.size = size # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def create_netlist(self): self.add_pins() @@ -33,12 +33,17 @@ class pand3(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 self.nand = factory.create(module_type="pnand3", - height=self.height) - self.add_mod(self.nand) + height=self.height, + add_wells=False) + # Add the well tap to the inverter because when stacked + # vertically it is sometimes narrower self.inv = factory.create(module_type="pdriver", size_list=[self.size], - height=self.height) + height=self.height, + add_wells=self.add_wells) + + self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index b36d3e95..266400c9 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -17,7 +17,7 @@ class pdriver(pgate.pgate): sized for driving a load. """ - def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None): + def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None, add_wells=True): debug.info(1, "creating pdriver {}".format(name)) @@ -35,7 +35,7 @@ class pdriver(pgate.pgate): debug.error("Cannot specify both size_list and neg_polarity.", -1) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height, add_wells) def compute_sizes(self): # size_list specified @@ -73,9 +73,9 @@ class pdriver(pgate.pgate): self.place_modules() self.route_wires() self.add_layout_pins() - self.width = self.inv_inst_list[-1].rx() self.height = self.inv_inst_list[0].height + self.extend_wells() self.route_supply_rails() self.add_boundary() @@ -87,7 +87,7 @@ class pdriver(pgate.pgate): def add_modules(self): self.inv_list = [] - add_well = True + add_well = self.add_wells for size in self.size_list: temp_inv = factory.create(module_type="pinv", size=size, diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index be317f48..cdb89fcb 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -162,12 +162,12 @@ class pgate(design.design): nwell_height = nwell_max_offset - self.nwell_y_offset self.add_rect(layer="nwell", offset=nwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=nwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=nwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=nwell_height) # Start this half a rail width below the cell @@ -178,12 +178,12 @@ class pgate(design.design): pwell_height = self.nwell_y_offset - pwell_position.y self.add_rect(layer="pwell", offset=pwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=pwell_height) if "vtg" in layer: self.add_rect(layer="vtg", offset=pwell_position, - width=self.well_width, + width=self.width + 2 * self.well_extend_active, height=pwell_height) def add_nwell_contact(self, pmos, pmos_pos): @@ -304,16 +304,17 @@ class pgate(design.design): def determine_width(self): """ Determine the width based on the well contacts (assumed to be on the right side) """ + # It was already set or is left as default (minimum) # Width is determined by well contact and spacing and allowing a supply via between each cell if self.add_wells: - self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width + width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width # Height is an input parameter, so it is not recomputed. else: max_active_xoffset = self.find_highest_layer_coords("active").x max_route_xoffset = self.find_highest_layer_coords(self.route_layer).x + 0.5 * self.m1_space - self.width = max(max_active_xoffset, max_route_xoffset) - - self.well_width = self.width + 2 * self.nwell_enclose_active + width = max(max_active_xoffset, max_route_xoffset) + + self.width = width @staticmethod def bin_width(tx_type, target_width): diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 214e66dc..465f7a58 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -60,14 +60,14 @@ class pinv(pgate.pgate): self.add_well_contacts() self.determine_width() self.extend_wells() - self.route_supply_rails() - self.connect_rails() self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", position="farleft") self.route_outputs() + self.route_supply_rails() + self.connect_rails() self.add_boundary() def add_pins(self): diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 59995133..51581e61 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.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 diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 9e38287f..e1d19534 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -50,7 +50,7 @@ class single_level_column_mux(pgate.pgate): self.connect_poly() self.add_bitline_pins() self.connect_bitlines() - self.add_wells() + self.add_pn_wells() def add_modules(self): self.bitcell = factory.create(module_type="bitcell") @@ -218,7 +218,7 @@ class single_level_column_mux(pgate.pgate): self.add_path(self.col_mux_stack[2], [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.center()]) - def add_wells(self): + def add_pn_wells(self): """ Add a well and implant over the whole cell. Also, add the pwell contact (if it exists) diff --git a/compiler/tests/04_pnor2_test.py b/compiler/tests/04_pnor2_test.py index 1e8abaa4..ea0d6dbc 100755 --- a/compiler/tests/04_pnor2_test.py +++ b/compiler/tests/04_pnor2_test.py @@ -25,10 +25,6 @@ class pnor2_test(openram_test): tx = factory.create(module_type="pnor2", size=1) self.local_check(tx) - debug.info(2, "Checking 2-input nor gate") - tx = factory.create(module_type="pnor2", size=1, add_wells=False) - self.local_drc_check(tx) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/04_single_level_column_mux_pbitcell_test.py b/compiler/tests/04_single_level_column_mux_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/05_replica_bitcell_array_test.py b/compiler/tests/05_replica_bitcell_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/08_wordline_driver_pbitcell_test.py b/compiler/tests/08_wordline_driver_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/09_sense_amp_array_test_pbitcell.py b/compiler/tests/09_sense_amp_array_test_pbitcell.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_pbitcell_test.py b/compiler/tests/10_write_driver_array_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py b/compiler/tests/10_write_driver_array_wmask_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_mask_and_array_pbitcell_test.py b/compiler/tests/10_write_mask_and_array_pbitcell_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/19_bank_select_pbitcell_test.py b/compiler/tests/19_bank_select_pbitcell_test.py old mode 100644 new mode 100755 From f8bcc543385ff8da82cf2d04c477714bcb8fc85e Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 May 2020 16:04:38 -0700 Subject: [PATCH 09/42] Determine width after routing with no well contacts. --- compiler/pgates/pnand3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 446b8212..85b3f0b6 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -65,12 +65,12 @@ class pnand3(pgate.pgate): self.place_ptx() if self.add_wells: self.add_well_contacts() + self.route_inputs() + self.route_output() self.determine_width() self.route_supply_rails() self.connect_rails() self.extend_wells() - self.route_inputs() - self.route_output() self.add_boundary() def add_ptx(self): From 4b526f0d5fd970a87628aadf85183045ab06cbe0 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 May 2020 16:54:26 -0700 Subject: [PATCH 10/42] Check min size inverter. --- compiler/modules/write_mask_and_array.py | 2 +- compiler/pgates/pinv.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index 4d4e0c50..e660d519 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -60,7 +60,7 @@ class write_mask_and_array(design.design): # 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=max(self.write_size / 4.0, 1)) self.add_mod(self.and2) def create_and2_array(self): diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 465f7a58..5e1aab7b 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -40,6 +40,7 @@ class pinv(pgate.pgate): self.add_comment("size: {}".format(size)) self.size = size + debug.check(self.size >= 1, "Must have a size greater than or equal to 1.") self.nmos_size = size self.pmos_size = beta * size self.beta = beta From a305d788d7635b4536ea43e0b2486660a59e5682 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 13 May 2020 16:54:35 -0700 Subject: [PATCH 11/42] Vertical gates need both well contacts. --- compiler/pgates/pand2.py | 2 +- compiler/pgates/pand3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index dfbdc3e7..41189876 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -32,7 +32,7 @@ class pand2(pgate.pgate): def create_modules(self): self.nand = factory.create(module_type="pnand2", height=self.height, - add_wells=False) + add_wells=self.vertical) self.inv = factory.create(module_type="pdriver", size_list=[self.size], diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index d7f0b00e..92429921 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -34,7 +34,7 @@ class pand3(pgate.pgate): # Shield the cap, but have at least a stage effort of 4 self.nand = factory.create(module_type="pnand3", height=self.height, - add_wells=False) + add_wells=self.vertical) # Add the well tap to the inverter because when stacked # vertically it is sometimes narrower From 7505fa5aef116220975e3670091e927cdd93ca37 Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Wed, 27 May 2020 20:03:11 -0700 Subject: [PATCH 12/42] update for end caps --- compiler/bitcells/col_cap_bitcell_1rw_1r.py | 43 ++++++ compiler/bitcells/row_cap_bitcell_1rw_1r.py | 43 ++++++ compiler/modules/bitcell_base_array.py | 15 +- compiler/modules/col_cap_array.py | 103 +++++++++++++ compiler/modules/replica_bitcell_array.py | 138 ++++++++++-------- compiler/modules/replica_column.py | 84 ++++++++--- compiler/modules/row_cap_array.py | 128 ++++++++++++++++ compiler/options.py | 21 +-- .../14_replica_bitcell_1rw_1r_array_test.py | 12 +- 9 files changed, 486 insertions(+), 101 deletions(-) create mode 100644 compiler/bitcells/col_cap_bitcell_1rw_1r.py create mode 100644 compiler/bitcells/row_cap_bitcell_1rw_1r.py create mode 100644 compiler/modules/col_cap_array.py create mode 100644 compiler/modules/row_cap_array.py diff --git a/compiler/bitcells/col_cap_bitcell_1rw_1r.py b/compiler/bitcells/col_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..315ad23f --- /dev/null +++ b/compiler/bitcells/col_cap_bitcell_1rw_1r.py @@ -0,0 +1,43 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class col_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + todo""" + + pin_names = [props.bitcell.cell_1rw1r.pin.bl0, + props.bitcell.cell_1rw1r.pin.br0, + props.bitcell.cell_1rw1r.pin.bl1, + props.bitcell.cell_1rw1r.pin.br1, + props.bitcell.cell_1rw1r.pin.vdd] + + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", + "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("col_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "col_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "col_cap_cell_1rw_1r") + debug.info(2, "Create col_cap bitcell 1rw+1r object") + + self.width = col_cap_bitcell_1rw_1r.width + self.height = col_cap_bitcell_1rw_1r.height + self.pin_map = col_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) diff --git a/compiler/bitcells/row_cap_bitcell_1rw_1r.py b/compiler/bitcells/row_cap_bitcell_1rw_1r.py new file mode 100644 index 00000000..b50629f0 --- /dev/null +++ b/compiler/bitcells/row_cap_bitcell_1rw_1r.py @@ -0,0 +1,43 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +import utils +from tech import GDS, layer +from tech import cell_properties as props +import bitcell_base + + +class row_cap_bitcell_1rw_1r(bitcell_base.bitcell_base): + """ + A single bit cell which is forced to store a 0. + This module implements the single memory cell used in the design. It + is a hand-made cell, so the layout and netlist should be available in + the technology library. """ + + pin_names = [props.bitcell.cell_1rw1r.pin.wl0, + props.bitcell.cell_1rw1r.pin.wl1, + props.bitcell.cell_1rw1r.pin.gnd] + + type_list = ["INPUT", "INPUT", "GROUND"] + + (width, height) = utils.get_libcell_size("row_cap_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "row_cap_cell_1rw_1r", + GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "row_cap_cell_1rw_1r") + debug.info(2, "Create row_cap bitcell 1rw+1r object") + + self.width = row_cap_bitcell_1rw_1r.width + self.height = row_cap_bitcell_1rw_1r.height + self.pin_map = row_cap_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) diff --git a/compiler/modules/bitcell_base_array.py b/compiler/modules/bitcell_base_array.py index a8829fc3..9b46a192 100644 --- a/compiler/modules/bitcell_base_array.py +++ b/compiler/modules/bitcell_base_array.py @@ -108,16 +108,23 @@ class bitcell_base_array(design.design): except AttributeError: bitcell_power_pin_directions = None + # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. + try: + bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via + except AttributeError: + bitcell_no_vdd_pin = False + # Add vdd/gnd via stacks for row in range(self.row_size): for col in range(self.column_size): 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(), - directions=bitcell_power_pin_directions, - start_layer=pin.layer) + if not (pin_name == "vdd" and bitcell_no_vdd_pin): + 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/col_cap_array.py b/compiler/modules/col_cap_array.py new file mode 100644 index 00000000..3119f4e5 --- /dev/null +++ b/compiler/modules/col_cap_array.py @@ -0,0 +1,103 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +from bitcell_base_array import bitcell_base_array +from sram_factory import factory +from globals import OPTS +from tech import cell_properties + +class col_cap_array(bitcell_base_array): + """ + Generate a dummy row/column for the replica array. + """ + def __init__(self, cols, rows, column_offset=0, mirror=0, name=""): + super().__init__(cols, rows, name, column_offset) + self.mirror = mirror + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + """ Create and connect the netlist """ + self.add_modules() + self.add_pins() + self.create_instances() + + def create_layout(self): + + self.place_array("dummy_r{0}_c{1}", self.mirror) + self.add_layout_pins() + self.add_boundary() + self.DRC_LVS() + + def add_modules(self): + """ Add the modules used in this design """ + # self.dummy_cell = factory.create(module_type="col_cap_bitcell_1rw_1r") # TODO: make module_type generic + self.dummy_cell = factory.create(module_type="col_cap_bitcell") + self.add_mod(self.dummy_cell) + + self.cell = factory.create(module_type="bitcell") + + def create_instances(self): + """ Create the module instances used in this design """ + self.cell_inst = {} + for col in range(self.column_size): + for row in range(self.row_size): + name = "bit_r{0}_c{1}".format(row, col) + self.cell_inst[row,col]=self.add_inst(name=name, + mod=self.dummy_cell) + self.connect_inst(self.get_bitcell_pins(col, row)) + + def get_bitcell_pins(self, col, row): + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ + + pin_name = cell_properties.bitcell.cell_1rw1r.pin + bitcell_pins = ["{0}_{1}".format(pin_name.bl0, col), + "{0}_{1}".format(pin_name.br0, col), + "{0}_{1}".format(pin_name.bl1, col), + "{0}_{1}".format(pin_name.br1, col), + "vdd"] + + return bitcell_pins + + def add_layout_pins(self): + """ Add the layout pins """ + + column_list = self.cell.get_all_bitline_names() + + for col in range(self.column_size): + for cell_column in column_list: + bl_pin = self.cell_inst[0,col].get_pin(cell_column) + self.add_layout_pin(text=cell_column+"_{0}".format(col), + layer=bl_pin.layer, + offset=bl_pin.ll().scale(1,0), + width=bl_pin.width(), + height=self.height) + + # Add vdd/gnd via stacks + for row in range(self.row_size): + for col in range(self.column_size): + 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(), + start_layer=pin.layer) + + + # def input_load(self): + # wl_wire = self.gen_wl_wire() + # return wl_wire.return_input_cap() + # + # def get_wordline_cin(self): + # """Get the relative input capacitance from the wordline connections in all the bitcell""" + # #A single wordline is connected to all the bitcells in a single row meaning the capacitance depends on the # of columns + # bitcell_wl_cin = self.cell.get_wl_cin() + # total_cin = bitcell_wl_cin * self.column_size + # return total_cin diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index c88dbe6d..ff91f7fd 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -1,12 +1,12 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc, spice +from tech import drc, spice, cell_properties from vector import vector from globals import OPTS from sram_factory import factory @@ -34,11 +34,11 @@ class replica_bitcell_array(design.design): debug.check(left_rbl+right_rbl==len(self.all_ports),"Invalid number of RBLs for port configuration.") debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.") - + # Two dummy rows/cols plus replica for each port self.extra_rows = 2 + left_rbl + right_rbl self.extra_cols = 2 + left_rbl + right_rbl - + self.create_netlist() if not OPTS.netlist_only: self.create_layout() @@ -46,8 +46,8 @@ class replica_bitcell_array(design.design): # We don't offset this because we need to align # the replica bitcell in the control logic #self.offset_all_coordinates() - - + + def create_netlist(self): """ Create and connect the netlist """ self.add_modules() @@ -55,15 +55,15 @@ class replica_bitcell_array(design.design): self.create_instances() def add_modules(self): - """ Array and dummy/replica columns + """ Array and dummy/replica columns d or D = dummy cell (caps to distinguish grouping) r or R = replica cell (caps to distinguish grouping) - b or B = bitcell - replica columns 1 + b or B = bitcell + replica columns 1 v v - bdDDDDDDDDDDDDDDdb <- Dummy row - bdDDDDDDDDDDDDDDrb <- Dummy row + bdDDDDDDDDDDDDDDdb <- Dummy row + bdDDDDDDDDDDDDDDrb <- Dummy row br--------------rb br| Array |rb br| row x col |rb @@ -106,7 +106,7 @@ class replica_bitcell_array(design.design): column_offset=column_offset, replica_bit=replica_bit) self.add_mod(self.replica_columns[bit]) - + # Dummy row self.dummy_row = factory.create(module_type="dummy_array", cols=self.column_size, @@ -116,15 +116,35 @@ class replica_bitcell_array(design.design): mirror=0) self.add_mod(self.dummy_row) - # Dummy col (mirror starting at first if odd replica+dummy rows) - self.dummy_col_left = factory.create(module_type="dummy_array", + + # If there are bitcell end caps, replace the dummy cells on the edge of the bitcell array with end caps. + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + + # Dummy Row or Col Cap, depending on bitcell array properties + edge_row_module_type = ("col_cap_array" if end_caps_enabled else "dummy_array") + + self.edge_row = factory.create(module_type=edge_row_module_type, + cols=self.column_size, + rows=1, + # dummy column + left replica column + column_offset=1 + self.left_rbl, + mirror=0) + self.add_mod(self.edge_row) + + # Dummy Col or Row Cap, depending on bitcell array properties + edge_col_module_type = ("row_cap_array" if end_caps_enabled else "dummy_array") + + self.edge_col_left = factory.create(module_type=edge_col_module_type, cols=1, column_offset=0, rows=self.row_size + self.extra_rows, mirror=(self.left_rbl+1)%2) - self.add_mod(self.dummy_col_left) + self.add_mod(self.edge_col_left) - self.dummy_col_right = factory.create(module_type="dummy_array", + self.edge_col_right = factory.create(module_type=edge_col_module_type, cols=1, # dummy column # + left replica column @@ -133,9 +153,7 @@ class replica_bitcell_array(design.design): column_offset=1 + self.left_rbl + self.column_size + self.right_rbl, rows=self.row_size + self.extra_rows, mirror=(self.left_rbl+1)%2) - self.add_mod(self.dummy_col_right) - - + self.add_mod(self.edge_col_right) def add_pins(self): self.bitcell_array_wl_names = self.bitcell_array.get_all_wordline_names() @@ -150,7 +168,7 @@ class replica_bitcell_array(design.design): self.rbl_bl_names = {} self.rbl_br_names = {} self.rbl_wl_names = {} - + # Create the full WL names include dummy, replica, and regular bit cells self.replica_col_wl_names = [] self.replica_col_wl_names.extend(["{0}_bot".format(x) for x in self.dummy_cell_wl_names]) @@ -193,7 +211,7 @@ class replica_bitcell_array(design.design): wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()] #wl_names[port] = "rbl_wl{}".format(port) self.replica_wl_names[port] = wl_names - + # External pins self.add_pin_list(self.bitcell_array_bl_names, "INOUT") @@ -204,22 +222,22 @@ class replica_bitcell_array(design.design): self.add_pin(bl_name,"OUTPUT") self.add_pin(br_name,"OUTPUT") self.add_pin_list(self.bitcell_array_wl_names, "INPUT") - # Need to sort by port order since dictionary values may not be in order + # Need to sort by port order since dictionary values may not be in order wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())] for pin_name in wl_names: self.add_pin(pin_name,"INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - + def create_instances(self): """ Create the module instances used in this design """ supplies = ["vdd", "gnd"] - + # Used for names/dimensions only self.cell = factory.create(module_type="bitcell") - + # Main array self.bitcell_array_inst=self.add_inst(name="bitcell_array", mod=self.bitcell_array) @@ -231,35 +249,33 @@ class replica_bitcell_array(design.design): self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port), mod=self.replica_columns[port]) self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies) - - + + # Dummy rows under the bitcell array (connected with with the replica cell wl) self.dummy_row_replica_inst = {} for port in range(self.left_rbl+self.right_rbl): self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port), mod=self.dummy_row) self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies) - - - # Top/bottom dummy rows + + + # Top/bottom dummy rows or col caps self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", - mod=self.dummy_row) + mod=self.edge_row) self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies) self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", - mod=self.dummy_row) + mod=self.edge_row) self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies) # Left/right Dummy columns self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", - mod=self.dummy_col_left) + mod=self.edge_col_left) self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.dummy_col_right_inst=self.add_inst(name="dummy_col_right", - mod=self.dummy_col_right) + mod=self.edge_col_right) self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) - - def create_layout(self): self.height = (self.row_size+self.extra_rows)*self.dummy_row.height @@ -267,7 +283,7 @@ class replica_bitcell_array(design.design): # This is a bitcell x bitcell offset to scale offset = vector(self.cell.width, self.cell.height) - + self.bitcell_array_inst.place(offset=[0,0]) # To the left of the bitcell array @@ -276,7 +292,6 @@ class replica_bitcell_array(design.design): # To the right of the bitcell array for bit in range(self.right_rbl): self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr()) - # Far top dummy row (first row above array is NOT flipped) flip_dummy = self.right_rbl%2 @@ -298,17 +313,17 @@ class replica_bitcell_array(design.design): for bit in range(self.right_rbl): self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(), mirror="MX" if bit%2 else "R0") - + self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl)) - + self.add_layout_pins() - + self.add_boundary() - + self.DRC_LVS() - + def add_layout_pins(self): """ Add the layout pins """ @@ -341,10 +356,10 @@ class replica_bitcell_array(design.design): for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]): # +1 for dummy row pin_bit = port+1 - # +row_size if above the array + # +row_size if above the array if port>=self.left_rbl: pin_bit += self.row_size - + pin_name += "_{}".format(pin_bit) pin = inst.get_pin(pin_name) if wl_name in self.rbl_wl_names.values(): @@ -368,20 +383,27 @@ class replica_bitcell_array(design.design): offset=pin.ll().scale(1, 0), width=pin.width(), height=self.height) - + + # For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps. + try: + bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via + except AttributeError: + bitcell_no_vdd_pin = False + 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(), - directions=("V", "V"), - start_layer=pin.layer) + if not (pin_name == "vdd" and bitcell_no_vdd_pin): + 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] - + def get_rbl_bl_name(self, port): """ Return the BL for the given RBL port """ return self.rbl_bl_names[port] @@ -393,17 +415,17 @@ class replica_bitcell_array(design.design): def analytical_power(self, corner, load): """Power of Bitcell array and bitline in nW.""" from tech import drc, parameter - + # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() 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) - - #Calculate the bitcell power which currently only includes leakage + + #Calculate the bitcell power which currently only includes leakage cell_power = self.cell.analytical_power(corner, load) - + #Leakage power grows with entire array and bitlines. total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, cell_power.leakage * self.column_size * self.row_size) @@ -429,13 +451,13 @@ class replica_bitcell_array(design.design): def graph_exclude_bits(self, targ_row, targ_col): """Excludes bits in column from being added to graph except target""" self.bitcell_array.graph_exclude_bits(targ_row, targ_col) - + def graph_exclude_replica_col_bits(self): """Exclude all replica/dummy cells in the replica columns except the replica bit.""" - + for port in range(self.left_rbl+self.right_rbl): self.replica_columns[port].exclude_all_but_replica() def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) + return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index d874ba8c..2e3d207e 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -1,11 +1,11 @@ # See LICENSE for licensing information. # -# Copyright (c) 2016-2019 Regents of the University of California +# Copyright (c) 2016-2019 Regents of the University of California # All rights reserved. # import debug import design -from tech import drc +from tech import drc, cell_properties import contact from sram_factory import factory from vector import vector @@ -16,7 +16,7 @@ class replica_column(design.design): Generate a replica bitline column for the replica array. Rows is the total number of rows i the main array. Left_rbl and right_rbl are the number of left and right replica bitlines. - Replica bit specifies which replica column this is (to determine where to put the + Replica bit specifies which replica column this is (to determine where to put the replica cell. """ @@ -31,15 +31,15 @@ class replica_column(design.design): # left, right, regular rows plus top/bottom dummy cells self.total_size = self.left_rbl+rows+self.right_rbl+2 self.column_offset = column_offset - + debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.") debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1, - "Replica bit cannot be in the regular array.") + "Replica bit cannot be in the regular array.") self.create_netlist() if not OPTS.netlist_only: self.create_layout() - + def create_netlist(self): self.add_modules() self.add_pins() @@ -47,7 +47,7 @@ class replica_column(design.design): def create_layout(self): self.height = self.total_size*self.cell.height - self.width = self.cell.width + self.width = self.cell.width self.place_instances() self.add_layout_pins() @@ -55,7 +55,7 @@ class replica_column(design.design): self.DRC_LVS() def add_pins(self): - + for bl_name in self.cell.get_all_bitline_names(): # In the replica column, these are only outputs! self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT") @@ -63,7 +63,7 @@ class replica_column(design.design): for row in range(self.total_size): for wl_name in self.cell.get_all_wl_names(): self.add_pin("{0}_{1}".format(wl_name,row), "INPUT") - + self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -72,27 +72,49 @@ class replica_column(design.design): self.add_mod(self.replica_cell) self.dummy_cell = factory.create(module_type="dummy_bitcell") self.add_mod(self.dummy_cell) + try: + edge_module_type = ("col_cap_bitcell" if cell_properties.bitcell.end_caps else "dummy_bitcell") + except AttributeError: + edge_module_type = "dummy_bitcell" + self.edge_cell = factory.create(module_type=edge_module_type) + self.add_mod(self.edge_cell) # Used for pin names only self.cell = factory.create(module_type="bitcell") - + def create_instances(self): + + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + self.cell_inst = {} for row in range(self.total_size): name="rbc_{0}".format(row) # Top/bottom cell are always dummy cells. # Regular array cells are replica cells (>left_rbl and self.left_rbl and row Date: Thu, 28 May 2020 20:31:21 -0700 Subject: [PATCH 13/42] fix for replica column mirroring over y --- compiler/modules/replica_bitcell_array.py | 6 ++++-- compiler/tests/14_replica_bitcell_1rw_1r_array_test.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index ff91f7fd..ff2260d3 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -91,14 +91,16 @@ class replica_bitcell_array(design.design): # Replica bitlines self.replica_columns = {} for bit in range(self.left_rbl+self.right_rbl): + # Creating left_rbl if bit Date: Thu, 14 May 2020 11:20:37 -0700 Subject: [PATCH 14/42] Thin-cell decoder changes. Add hard decoder gates (nand, inv, pnan) Add conditions for routing using LI layer in s8. Generalize bus layers for decoders. Move custom cells to own directory. Fixed via directions, etc. Add 4x16 hierarchical decoder and test case --- .gitlab-ci.yml | 11 +- compiler/base/contact.py | 8 +- compiler/base/geometry.py | 14 +- compiler/base/hierarchy_layout.py | 58 +-- compiler/base/hierarchy_spice.py | 1 + compiler/bitcells/bitcell.py | 2 +- compiler/{modules => custom}/dff.py | 4 +- compiler/custom/inv_dec.py | 80 ++++ compiler/custom/nand2_dec.py | 77 ++++ compiler/custom/nand3_dec.py | 77 ++++ compiler/custom/nand4_dec.py | 77 ++++ compiler/custom/pand2_dec.py | 138 +++++++ compiler/custom/pand3_dec.py | 149 +++++++ compiler/custom/pand4_dec.py | 149 +++++++ compiler/{modules => custom}/tri_gate.py | 0 compiler/{modules => custom}/write_driver.py | 0 compiler/modules/hierarchical_decoder.py | 373 ++++++++---------- compiler/modules/hierarchical_predecode.py | 260 ++++++++---- compiler/modules/hierarchical_predecode2x4.py | 20 +- compiler/modules/hierarchical_predecode3x8.py | 21 +- .../modules/hierarchical_predecode4x16.py | 64 +++ compiler/modules/port_address.py | 27 +- compiler/modules/port_data.py | 59 ++- ...ine_driver.py => wordline_driver_array.py} | 153 +++---- compiler/options.py | 4 + compiler/pgates/pand2.py | 2 +- compiler/pgates/pdriver.py | 12 +- compiler/pgates/pgate.py | 1 - compiler/pgates/pinv_dec.py | 217 ++++++++++ compiler/pgates/pnand3.py | 3 + compiler/pgates/ptx.py | 10 +- compiler/pgates/wordline_driver.py | 155 ++++++++ compiler/sram_factory.py | 4 +- compiler/tests/02_library_lvs_test.py | 2 +- compiler/tests/04_pand2_dec_test.py | 39 ++ compiler/tests/04_pand3_dec_test.py | 39 ++ compiler/tests/04_pand4_dec_test.py | 39 ++ compiler/tests/04_pdriver_test.py | 4 +- compiler/tests/04_pinv_dec_1x_test.py | 35 ++ ...ver_test.py => 04_wordline_driver_test.py} | 2 +- .../tests/06_hierarchical_decoder_test.py | 8 +- .../06_hierarchical_predecode2x4_test.py | 7 +- .../06_hierarchical_predecode3x8_test.py | 7 +- .../06_hierarchical_predecode4x16_test.py | 42 ++ ...08_wordline_driver_array_pbitcell_test.py} | 5 +- .../tests/08_wordline_driver_array_test.py | 37 ++ compiler/tests/18_port_data_1rw_1r_test.py | 6 +- compiler/tests/19_single_bank_test.py | 40 +- compiler/tests/testutils.py | 1 + 49 files changed, 1991 insertions(+), 552 deletions(-) rename compiler/{modules => custom}/dff.py (95%) create mode 100644 compiler/custom/inv_dec.py create mode 100644 compiler/custom/nand2_dec.py create mode 100644 compiler/custom/nand3_dec.py create mode 100644 compiler/custom/nand4_dec.py create mode 100644 compiler/custom/pand2_dec.py create mode 100644 compiler/custom/pand3_dec.py create mode 100644 compiler/custom/pand4_dec.py rename compiler/{modules => custom}/tri_gate.py (100%) rename compiler/{modules => custom}/write_driver.py (100%) create mode 100644 compiler/modules/hierarchical_predecode4x16.py rename compiler/modules/{wordline_driver.py => wordline_driver_array.py} (50%) create mode 100644 compiler/pgates/pinv_dec.py create mode 100644 compiler/pgates/wordline_driver.py create mode 100755 compiler/tests/04_pand2_dec_test.py create mode 100755 compiler/tests/04_pand3_dec_test.py create mode 100755 compiler/tests/04_pand4_dec_test.py create mode 100755 compiler/tests/04_pinv_dec_1x_test.py rename compiler/tests/{08_wordline_driver_test.py => 04_wordline_driver_test.py} (93%) create mode 100755 compiler/tests/06_hierarchical_predecode4x16_test.py rename compiler/tests/{08_wordline_driver_pbitcell_test.py => 08_wordline_driver_array_pbitcell_test.py} (87%) create mode 100755 compiler/tests/08_wordline_driver_array_test.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27c341aa..27431cb2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ before_script: - . /home/gitlab-runner/setup-paths.sh - export OPENRAM_HOME="`pwd`/compiler" - - export OPENRAM_TECH="`pwd`/technology" + - export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech" stages: - test @@ -25,6 +25,15 @@ scn4m_subm: - .coverage.* expire_in: 1 week +# s8: +# stage: test +# script: +# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8 +# artifacts: +# paths: +# - .coverage.* +# expire_in: 1 week + coverage: stage: coverage script: diff --git a/compiler/base/contact.py b/compiler/base/contact.py index f4fda552..14dbf76e 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -53,6 +53,10 @@ class contact(hierarchy_design.hierarchy_design): 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) + # Preferred directions + elif directions == "pref": + self.directions = (tech.preferred_directions[layer_stack[0]], + tech.preferred_directions[layer_stack[2]]) # User directions elif directions: self.directions = directions @@ -149,7 +153,7 @@ class contact(hierarchy_design.hierarchy_design): self.first_layer_vertical_enclosure = max(self.first_layer_enclosure, (self.first_layer_minwidth - self.contact_array_height) / 2) else: - debug.error("Invalid first layer direction.", -1) + debug.error("Invalid first layer direction: ".format(self.directions[0]), -1) # In some technologies, the minimum width may be larger # than the overlap requirement around the via, so @@ -165,7 +169,7 @@ class contact(hierarchy_design.hierarchy_design): self.second_layer_vertical_enclosure = max(self.second_layer_enclosure, (self.second_layer_minwidth - self.contact_array_width) / 2) else: - debug.error("Invalid second layer direction.", -1) + debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1) def create_contact_array(self): """ Create the contact array at the origin""" diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 96da3d99..32af7ee9 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -86,8 +86,14 @@ class geometry: elif mirror == "XY": ll = ll.scale(-1, -1) ur = ur.scale(-1, -1) + elif mirror == "" or mirror == "R0": + pass + else: + debug.error("Invalid mirroring: {}".format(mirror), -1) - if rotate == 90: + if rotate == 0: + pass + elif rotate == 90: ll = ll.rotate_scale(-1, 1) ur = ur.rotate_scale(-1, 1) elif rotate == 180: @@ -96,6 +102,8 @@ class geometry: elif rotate == 270: ll = ll.rotate_scale(1, -1) ur = ur.rotate_scale(1, -1) + else: + debug.error("Invalid rotation: {}".format(rotate), -1) self.boundary = [offset + ll, offset + ur] self.normalize() @@ -139,6 +147,10 @@ class geometry: def cy(self): """ Return the center y """ return 0.5 * (self.boundary[0].y + self.boundary[1].y) + + def center(self): + """ Return the center coordinate """ + return vector(self.cx(), self.cy()) class instance(geometry): diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index df7095df..4d0345a3 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -244,25 +244,27 @@ class layout(): height)) return self.objs[-1] - def add_segment_center(self, layer, start, end): + def add_segment_center(self, layer, start, end, width=None): """ Add a min-width rectanglular segment using center line on the start to end point """ - minwidth_layer = drc["minwidth_{}".format(layer)] + if not width: + width = drc["minwidth_{}".format(layer)] + if start.x != end.x and start.y != end.y: debug.error("Nonrectilinear center rect!", -1) elif start.x != end.x: - offset = vector(0, 0.5 * minwidth_layer) + offset = vector(0, 0.5 * width) return self.add_rect(layer, start - offset, end.x - start.x, - minwidth_layer) + width) else: - offset = vector(0.5 * minwidth_layer, 0) + offset = vector(0.5 * width, 0) return self.add_rect(layer, start - offset, - minwidth_layer, + width, end.y - start.y) def get_pin(self, text): @@ -322,7 +324,7 @@ class layout(): for pin_name in self.pin_map.keys(): self.copy_layout_pin(instance, pin_name, prefix + pin_name) - def add_layout_pin_segment_center(self, text, layer, start, end): + def add_layout_pin_segment_center(self, text, layer, start, end, width=None): """ Creates a path like pin with center-line convention """ @@ -331,27 +333,27 @@ class layout(): self.gds_write(file_name) debug.error("Cannot have a non-manhatten layout pin: {}".format(file_name), -1) - minwidth_layer = drc["minwidth_{}".format(layer)] + if not width: + layer_width = drc["minwidth_{}".format(layer)] + else: + layer_width = width # one of these will be zero - width = max(start.x, end.x) - min(start.x, end.x) - height = max(start.y, end.y) - min(start.y, end.y) + bbox_width = max(start.x, end.x) - min(start.x, end.x) + bbox_height = max(start.y, end.y) - min(start.y, end.y) ll_offset = vector(min(start.x, end.x), min(start.y, end.y)) # Shift it down 1/2 a width in the 0 dimension - if height == 0: - ll_offset -= vector(0, 0.5 * minwidth_layer) - if width == 0: - ll_offset -= vector(0.5 * minwidth_layer, 0) - # This makes sure it is long enough, but also it is not 0 width! - height = max(minwidth_layer, height) - width = max(minwidth_layer, width) + if bbox_height == 0: + ll_offset -= vector(0, 0.5 * layer_width) + if bbox_width == 0: + ll_offset -= vector(0.5 * layer_width, 0) - return self.add_layout_pin(text, - layer, - ll_offset, - width, - height) + return self.add_layout_pin(text=text, + layer=layer, + offset=ll_offset, + width=bbox_width, + height=bbox_height) def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ @@ -692,6 +694,8 @@ class layout(): boundary_layer = "stdc" boundary = [self.find_lowest_coords(), self.find_highest_coords()] + debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.") + height = boundary[1][1] - boundary[0][1] width = boundary[1][0] - boundary[0][0] (layer_number, layer_purpose) = techlayer[boundary_layer] @@ -1306,16 +1310,16 @@ class layout(): which vias are needed. """ - via = self.add_via_stack_center(from_layer=start_layer, - to_layer=self.pwr_grid_layer, - size=size, - offset=loc, - directions=directions) if start_layer == self.pwr_grid_layer: self.add_layout_pin_rect_center(text=name, layer=self.pwr_grid_layer, offset=loc) else: + via = self.add_via_stack_center(from_layer=start_layer, + to_layer=self.pwr_grid_layer, + size=size, + offset=loc, + directions=directions) # Hack for min area if OPTS.tech_name == "s8": width = round_to_grid(sqrt(drc["minarea_m3"])) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 8091af63..00d7ad44 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -208,6 +208,7 @@ class spice(): # parses line into ports and remove subckt self.pins = subckt_line.split(" ")[2:] else: + debug.info(4, "no spfile {0}".format(self.sp_file)) self.spice = [] # We don't define self.lvs and will use self.spice if dynamically created diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index b0e79208..4ed2d053 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -7,7 +7,7 @@ # import debug import utils -from tech import GDS, layer, parameter +from tech import GDS, layer from tech import cell_properties as props import bitcell_base diff --git a/compiler/modules/dff.py b/compiler/custom/dff.py similarity index 95% rename from compiler/modules/dff.py rename to compiler/custom/dff.py index e319459e..c8fdb4b0 100644 --- a/compiler/modules/dff.py +++ b/compiler/custom/dff.py @@ -61,7 +61,7 @@ class dff(design.design): #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. return parameter["dff_clk_cin"] - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + self.add_graph_edges(graph, port_nets) diff --git a/compiler/custom/inv_dec.py b/compiler/custom/inv_dec.py new file mode 100644 index 00000000..80fdb74e --- /dev/null +++ b/compiler/custom/inv_dec.py @@ -0,0 +1,80 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils +import debug + + +class inv_dec(design.design): + """ + INV for address decoders. + """ + + pin_names = ["A", "Z", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("inv_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "inv_dec", GDS["unit"]) + + def __init__(self, name="inv_dec", height=None): + design.design.__init__(self, name) + + self.width = inv_dec.width + self.height = inv_dec.height + self.pin_map = inv_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["inv_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + + return transition_prob * (c_load + c_para) + + def input_load(self): + """ + Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor + """ + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 1 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/custom/nand2_dec.py b/compiler/custom/nand2_dec.py new file mode 100644 index 00000000..30a018a1 --- /dev/null +++ b/compiler/custom/nand2_dec.py @@ -0,0 +1,77 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils + + +class nand2_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand2_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand2_dec", GDS["unit"]) + + def __init__(self, name="nand2_dec", height=None): + design.design.__init__(self, name) + + self.width = nand2_dec.width + self.height = nand2_dec.height + self.pin_map = nand2_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand2_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/custom/nand3_dec.py b/compiler/custom/nand3_dec.py new file mode 100644 index 00000000..d700062c --- /dev/null +++ b/compiler/custom/nand3_dec.py @@ -0,0 +1,77 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils + + +class nand3_dec(design.design): + """ + 3-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand3_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand3_dec", GDS["unit"]) + + def __init__(self, name="nand3_dec", height=None): + design.design.__init__(self, name) + + self.width = nand3_dec.width + self.height = nand3_dec.height + self.pin_map = nand3_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand3_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/custom/nand4_dec.py b/compiler/custom/nand4_dec.py new file mode 100644 index 00000000..5c2bb882 --- /dev/null +++ b/compiler/custom/nand4_dec.py @@ -0,0 +1,77 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import design +from tech import GDS, layer, spice, parameter +import logical_effort +import utils + + +class nand4_dec(design.design): + """ + 2-input NAND decoder for address decoders. + """ + + pin_names = ["A", "B", "C", "D", "Z", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] + + (width, height) = utils.get_libcell_size("nand4_dec", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "nand4_dec", GDS["unit"]) + + def __init__(self, name="nand4_dec", height=None): + design.design.__init__(self, name) + + self.width = nand4_dec.width + self.height = nand4_dec.height + self.pin_map = nand4_dec.pin_map + self.add_pin_types(self.type_list) + + def analytical_power(self, corner, load): + """Returns dynamic and leakage power. Results in nW""" + c_eff = self.calculate_effective_capacitance(load) + freq = spice["default_event_frequency"] + power_dyn = self.calc_dynamic_power(corner, c_eff, freq) + power_leak = spice["nand4_leakage"] + + total_power = self.return_power(power_dyn, power_leak) + return total_power + + def calculate_effective_capacitance(self, load): + """Computes effective capacitance. Results in fF""" + c_load = load + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) + transition_prob = 0.1875 + return transition_prob * (c_load + c_para) + + def input_load(self): + """Return the relative input capacitance of a single input""" + return self.nmos_size + self.pmos_size + + def get_stage_effort(self, cout, inp_is_rise=True): + """ + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) + + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/custom/pand2_dec.py b/compiler/custom/pand2_dec.py new file mode 100644 index 00000000..0db490ff --- /dev/null +++ b/compiler/custom/pand2_dec.py @@ -0,0 +1,138 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import pgate +from sram_factory import factory +from globals import OPTS + + +class pand2_dec(pgate.pgate): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + debug.info(1, "Creating pand2_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + + self.size = size + + pgate.pgate.__init__(self, name, height, add_wells) + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + if OPTS.tech_name == "s8": + self.nand = factory.create(module_type="nand2_dec") + else: + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + self.width = self.nand.width + self.inv.width + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand2_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand2_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "s8": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + 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 add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "s8": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/pand3_dec.py b/compiler/custom/pand3_dec.py new file mode 100644 index 00000000..3ec1eb45 --- /dev/null +++ b/compiler/custom/pand3_dec.py @@ -0,0 +1,149 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import pgate +from sram_factory import factory +from globals import OPTS + + +class pand3_dec(pgate.pgate): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + debug.info(1, "Creating pand3_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + + self.size = size + + pgate.pgate.__init__(self, name, height, add_wells) + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + if OPTS.tech_name == "s8": + self.nand = factory.create(module_type="nand3_dec") + else: + self.nand = factory.create(module_type="nand3_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + height=self.height, + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + self.width = self.nand.width + self.inv.width + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand3_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand3_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "s8": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + 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 add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "s8": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/custom/pand4_dec.py b/compiler/custom/pand4_dec.py new file mode 100644 index 00000000..ee65a349 --- /dev/null +++ b/compiler/custom/pand4_dec.py @@ -0,0 +1,149 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import pgate +from sram_factory import factory +from globals import OPTS + + +class pand4_dec(pgate.pgate): + """ + This is an AND with configurable drive strength. + """ + def __init__(self, name, size=1, height=None, add_wells=True): + debug.info(1, "Creating pand4_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + + self.size = size + + pgate.pgate.__init__(self, name, height, add_wells) + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + if OPTS.tech_name == "s8": + self.nand = factory.create(module_type="nand4_dec") + else: + self.nand = factory.create(module_type="nand4_dec", + height=self.height) + + self.inv = factory.create(module_type="inv_dec", + size=self.size) + + self.add_mod(self.nand) + self.add_mod(self.inv) + + def create_layout(self): + self.width = self.nand.width + self.inv.width + + self.place_insts() + self.add_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("C", "INPUT") + self.add_pin("D", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="pand4_dec_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "D", "zb_int", "vdd", "gnd"]) + + self.inv_inst = self.add_inst(name="pand4_dec_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "s8": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.inv_inst]: + self.copy_layout_pin(inst, name) + else: + 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 add_wires(self): + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.inv_inst.get_pin("A") + if OPTS.tech_name == "s8": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.inv_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B", "C"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def analytical_delay(self, corner, slew, load=0.0): + """ Calculate the analytical delay of DFF-> INV -> INV """ + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) + return nand_delay + inv_delay + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.inv.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + last_stage_is_rise = stage1.is_rise + + stage2 = self.inv.get_stage_effort(external_cout, last_stage_is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/modules/tri_gate.py b/compiler/custom/tri_gate.py similarity index 100% rename from compiler/modules/tri_gate.py rename to compiler/custom/tri_gate.py diff --git a/compiler/modules/write_driver.py b/compiler/custom/write_driver.py similarity index 100% rename from compiler/modules/write_driver.py rename to compiler/custom/write_driver.py diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index c1593932..3cb5d35c 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -12,7 +12,6 @@ from sram_factory import factory from vector import vector from globals import OPTS from errors import drc_error -from tech import cell_properties, layer class hierarchical_decoder(design.design): @@ -28,12 +27,8 @@ class hierarchical_decoder(design.design): self.pre3x8_inst = [] 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 - + self.cell_height = b.height + self.num_outputs = num_outputs self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) @@ -41,41 +36,6 @@ class hierarchical_decoder(design.design): self.create_netlist() if not OPTS.netlist_only: self.create_layout() - - def find_decoder_height(self): - """ - Dead code. This would dynamically determine the bitcell multiple, - but I just decided to hard code it in the tech file if it is not 1 - because a DRC tool would be required even to run in front-end mode. - """ - b = factory.create(module_type="bitcell") - - # Old behavior - if OPTS.netlist_only: - return (b.height, 1) - - # Search for the smallest multiple that works - cell_multiple = 1 - while cell_multiple < 5: - cell_height = cell_multiple * b.height - # debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height)) - try: - and3 = factory.create(module_type="pand3", - height=cell_height) - except drc_error: - # debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height)) - pass - else: - (drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True) - total_errors = drc_errors + lvs_errors - if total_errors == 0: - debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple)) - return (cell_height, cell_multiple) - - cell_multiple += 1 - - else: - debug.error("Couldn't find a valid decoder height multiple.", -1) def create_netlist(self): self.add_modules() @@ -97,15 +57,22 @@ class hierarchical_decoder(design.design): self.DRC_LVS() def add_modules(self): - self.inv = factory.create(module_type="pinv", - height=self.cell_height) - self.add_mod(self.inv) - self.and2 = factory.create(module_type="pand2", - height=self.cell_height) + if OPTS.tech_name == "s8": + self.and2 = factory.create(module_type="pand2_dec") + else: + self.and2 = factory.create(module_type="pand2_dec", + height=self.cell_height) self.add_mod(self.and2) - self.and3 = factory.create(module_type="pand3", - height=self.cell_height) + if OPTS.tech_name == "s8": + self.and3 = factory.create(module_type="pand3_dec") + else: + self.and3 = factory.create(module_type="pand3_dec", + height=self.cell_height) + self.add_mod(self.and3) + # TBD + # self.and4 = factory.create(module_type="pand4_dec") + # self.add_mod(self.and4) self.add_decoders() @@ -176,56 +143,63 @@ class hierarchical_decoder(design.design): -1) # Calculates height and width of pre-decoder, - if self.no_of_pre3x8 > 0: + # FIXME: Update with 4x16 + if self.no_of_pre3x8 > 0 and self.no_of_pre2x4 > 0: + self.predecoder_width = max(self.pre3_8.width, self.pre2_4.width) + elif self.no_of_pre3x8 > 0: self.predecoder_width = self.pre3_8.width else: self.predecoder_width = self.pre2_4.width - - self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 - # We may have more than one bitcell per decoder row - 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 + # How much space between each predecoder + self.predecoder_spacing = self.and2.height + self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 \ + + (self.no_of_pre2x4 + self.no_of_pre3x8 - 1) * self.predecoder_spacing + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "s8": + self.bus_layer = "m1" + self.bus_directions = "nonpref" + self.bus_pitch = self.m1_pitch + self.bus_space = self.m2_space + self.input_layer = "m2" + self.output_layer = "li" + self.output_layer_pitch = self.li_pitch else: - debug.error("Insufficient layers for multi-bit height decoder.", -1) + self.bus_layer = "m2" + self.bus_directions = "pref" + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # These two layers being the same requires a special jog + # to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m3" + self.output_layer_pitch = self.m3_pitch + + # Two extra pitches between modules on left and right + self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch + self.row_decoder_height = self.and2.height * self.num_outputs + # Extra bus space for supply contacts + self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space # 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 - 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.m2_pitch - self.width = self.input_routing_width + self.predecoder_width \ + self.height = max(self.predecoder_height, self.row_decoder_height) + self.bus_space + if (self.num_inputs == 4 or self.num_inputs == 5): + self.nand_width = self.and2.width + else: + self.nand_width = self.and3.width + + self.width = self.input_routing_width \ + + self.predecoder_width \ + self.internal_routing_width \ - + self.decoders_per_row * nand_width + self.inv.width - + + self.nand_width \ + + self.m1_space + def route_inputs(self): """ Create input bus for the predecoders """ - # inputs should be as high as the decoders - input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height - # Find the left-most predecoder min_x = 0 if self.no_of_pre2x4 > 0: @@ -235,10 +209,10 @@ class hierarchical_decoder(design.design): input_offset=vector(min_x - self.input_routing_width, 0) input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)] - self.input_bus = self.create_vertical_pin_bus(layer="m2", + self.input_bus = self.create_vertical_pin_bus(layer=self.bus_layer, offset=input_offset, names=input_bus_names, - length=input_height) + length=self.predecoder_height) self.route_input_to_predecodes() @@ -253,9 +227,7 @@ class hierarchical_decoder(design.design): in_name = "in_{}".format(i) decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name) - # 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 + self.m1_pitch)) + decoder_offset = decoder_pin.center() input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -269,9 +241,7 @@ class hierarchical_decoder(design.design): in_name = "in_{}".format(i) decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name) - # 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 + self.m1_pitch)) + decoder_offset = decoder_pin.center() input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) self.route_input_bus(decoder_offset, input_offset) @@ -282,13 +252,14 @@ class hierarchical_decoder(design.design): vertical M2 coordinate to the predecode inputs """ - self.add_via_stack_center(from_layer="m2", - to_layer="m3", + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, 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]) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.input_layer, + offset=output_offset, + directions=self.bus_directions) + self.add_path(self.input_layer, [input_offset, output_offset]) def add_pins(self): """ Add the module pins """ @@ -363,19 +334,19 @@ class hierarchical_decoder(design.design): if (self.num_inputs == 2): base = vector(-self.pre2_4.width, 0) else: - base= vector(-self.pre2_4.width, num * self.pre2_4.height) + base= vector(-self.pre2_4.width, num * (self.pre2_4.height + self.predecoder_spacing)) - self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0)) + self.pre2x4_inst[num].place(base) def place_pre3x8(self, num): """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ if (self.num_inputs == 3): offset = vector(-self.pre_3_8.width, 0) else: - height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height + height = self.no_of_pre2x4 * (self.pre2_4.height + self.predecoder_spacing) + num * (self.pre3_8.height + self.predecoder_spacing) offset = vector(-self.pre3_8.width, height) - self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0)) + self.pre3x8_inst[num].place(offset) def create_row_decoder(self): """ Create the row-decoder by placing AND2/AND3 and Inverters @@ -431,7 +402,6 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): self.place_decoder_and_array() - def place_decoder_and_array(self): """ Add a column of AND gates for final decode. @@ -452,9 +422,7 @@ class hierarchical_decoder(design.design): Add a column of AND gates for the decoder above the predecoders. """ - for inst_index in range(self.num_outputs): - row = math.floor(inst_index / self.decoders_per_row) - dec = inst_index % self.decoders_per_row + for row in range(self.num_outputs): if ((row % 2) == 0): y_off = and_mod.height * row mirror = "R0" @@ -462,32 +430,16 @@ class hierarchical_decoder(design.design): y_off = and_mod.height * (row + 1) mirror = "MX" - x_off = self.internal_routing_width + dec * and_mod.width - self.and_inst[inst_index].place(offset=vector(x_off, y_off), - mirror=mirror) + x_off = self.internal_routing_width + self.and_inst[row].place(offset=vector(x_off, y_off), + mirror=mirror) def route_outputs(self): """ Add the pins. """ - 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())) + for row in range(self.num_outputs): + and_inst = self.and_inst[row] + self.copy_layout_pin(and_inst, "Z", "decode_{0}".format(row)) def route_decoder_bus(self): """ @@ -498,9 +450,9 @@ class hierarchical_decoder(design.design): if (self.num_inputs >= 4): # 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.decoder_bus_pitch, - offset=vector(0, 0), + self.predecode_bus = self.create_vertical_pin_bus(layer=self.bus_layer, + pitch=self.bus_pitch, + offset=vector(self.bus_pitch, 0), names=input_bus_names, length=self.height) @@ -518,8 +470,9 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(pre_num * 4 + i) out_name = "out_{}".format(i) pin = self.pre2x4_inst[pre_num].get_pin(out_name) - x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch - self.route_predecode_bus_inputs(predecode_name, pin, x_offset) + x_offset = self.pre2x4_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre2x4_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre3x8): @@ -527,8 +480,9 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4) out_name = "out_{}".format(i) pin = self.pre3x8_inst[pre_num].get_pin(out_name) - x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch - self.route_predecode_bus_inputs(predecode_name, pin, x_offset) + x_offset = self.pre3x8_inst[pre_num].rx() + self.output_layer_pitch + y_offset = self.pre3x8_inst[pre_num].by() + i * self.cell_height + self.route_predecode_bus_inputs(predecode_name, pin, x_offset, y_offset) def route_bus_to_decoder(self): """ @@ -542,12 +496,6 @@ class hierarchical_decoder(design.design): and the 128th AND3 is connected to [3,7,15] """ 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]: @@ -557,13 +505,11 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - output_index, - 0) + output_index) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - output_index, - 1) + output_index) output_index = output_index + 1 elif (self.num_inputs > 5): @@ -575,18 +521,15 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(index_A) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("A"), - output_index, - 0) + output_index) predecode_name = "predecode_{}".format(index_B) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("B"), - output_index, - 1) + output_index) predecode_name = "predecode_{}".format(index_C) self.route_predecode_bus_outputs(predecode_name, self.and_inst[output_index].get_pin("C"), - output_index, - 2) + output_index) output_index = output_index + 1 def route_vdd_gnd(self): @@ -594,90 +537,90 @@ class hierarchical_decoder(design.design): Add a pin for each row of vdd/gnd which are must-connects next level up. """ + + if OPTS.tech_name == "s8": + for n in ["vdd", "gnd"]: + pins = self.and_inst[0].get_pins(n) + for pin in pins: + self.add_rect(layer=pin.layer, + offset=pin.ll() + vector(0, self.bus_space), + width=pin.width(), + height=self.height - 2 * self.bus_space) - # The vias will be placed at the right of the cells. - xoffset = max(x.rx() for x in self.and_inst) - for num in range(0, self.num_outputs): - # Only add the power pin for the 1st in each row - if num % self.decoders_per_row: - continue - - for pin_name in ["vdd", "gnd"]: - # 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(supply_pin.layer, - [supply_pin.lc(), vector(xoffset, supply_pin.cy())]) - self.add_power_pin(name=pin_name, - 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") + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.and_inst[:-1]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + for i in self.pre2x4_inst + self.pre3x8_inst: + self.copy_layout_pin(i, n) + else: + # The vias will be placed at the right of the cells. + xoffset = max(x.rx() for x in self.and_inst) + 0.5 * self.m1_space + for row in range(0, self.num_outputs): + for pin_name in ["vdd", "gnd"]: + # The nand and inv are the same height rows... + supply_pin = self.and_inst[row].get_pin(pin_name) + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name=pin_name, + loc=pin_pos, + start_layer=supply_pin.layer) + + # Copy the pins from the predecoders + for pre in self.pre2x4_inst + self.pre3x8_inst: + for pin_name in ["vdd", "gnd"]: + self.copy_layout_pin(pre, pin_name) - def route_predecode_bus_outputs(self, rail_name, pin, output_index, pin_index): + def route_predecode_bus_outputs(self, rail_name, pin, row): """ Connect the routing rail to the given metal1 pin using a routing track at the given y_offset """ - 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]) + rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) + self.add_path(self.input_layer, [rail_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=self.bus_layer, + to_layer=self.input_layer, + offset=rail_pos, + directions=self.bus_directions) self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.decoder_layers[row_remainder][2], + to_layer=self.input_layer, offset=pin_pos, directions=("H", "H")) - def route_predecode_bus_inputs(self, rail_name, pin, x_offset): + def route_predecode_bus_inputs(self, rail_name, pin, x_offset, y_offset): """ Connect the routing rail to the given metal1 pin using a jog to the right of the cell at the given x_offset. """ # 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.rc() + mid_point1 = vector(x_offset, pin_pos.y) + mid_point2 = vector(x_offset, y_offset) + rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) + self.add_path(self.output_layer, [pin_pos, mid_point1, mid_point2, rail_pos]) - pin_pos = pin.center() - rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) - self.add_path("m1", [pin_pos, rail_pos]) + # pin_pos = pin.center() + # rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) + # self.add_path(self.output_layer, [pin_pos, rail_pos]) self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.output_layer, offset=pin_pos) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=rail_pos) + self.add_via_stack_center(from_layer=self.bus_layer, + to_layer=self.output_layer, + offset=rail_pos, + directions=self.bus_directions) 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 9a658546..8244028e 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -8,29 +8,24 @@ import debug import design import math -import contact from vector import vector from sram_factory import factory -from tech import cell_properties +from globals import OPTS class hierarchical_predecode(design.design): """ - Pre 2x4 and 3x8 decoder shared code. + Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. """ def __init__(self, name, input_number, height=None): self.number_of_inputs = input_number 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 + self.cell_height = b.height else: self.cell_height = height - + self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) design.design.__init__(self, name) @@ -44,34 +39,73 @@ class hierarchical_predecode(design.design): def add_modules(self): """ Add the INV and AND gate modules """ - - self.inv = factory.create(module_type="pinv", - height=self.cell_height) - self.add_mod(self.inv) - - self.add_and(self.number_of_inputs) - self.add_mod(self.and_mod) - def add_and(self, inputs): - """ Create the NAND for the predecode input stage """ - if inputs==2: - self.and_mod = factory.create(module_type="pand2", + # FIXME: Default parms are required for hard cells for now. + if self.number_of_inputs == 2: + self.and_mod = factory.create(module_type="pand2_dec", height=self.cell_height) - elif inputs==3: - self.and_mod = factory.create(module_type="pand3", + elif self.number_of_inputs == 3: + self.and_mod = factory.create(module_type="pand3_dec", + height=self.cell_height) + elif self.number_of_inputs == 4: + self.and_mod = factory.create(module_type="pand4_dec", height=self.cell_height) else: - debug.error("Invalid number of predecode inputs: {}".format(inputs), -1) - + debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1) + self.add_mod(self.and_mod) + + # This uses the pinv_dec parameterized cell + self.inv = factory.create(module_type="inv_dec", + height=self.cell_height, + size=1) + self.add_mod(self.inv) + + + def create_layout(self): + """ The general organization is from left to right: + 1) a set of M2 rails for input signals + 2) a set of inverters to invert input signals + 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs + 4) a set of AND gates for inversion + """ + self.setup_layout_constraints() + self.route_rails() + self.place_input_inverters() + self.place_and_array() + self.route() + self.add_boundary() + self.DRC_LVS() + def setup_layout_constraints(self): + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "s8": + self.bus_layer = "m1" + self.bus_directions = None + self.bus_pitch = self.m1_pitch + self.bus_space = 1.5 * self.m1_space + self.input_layer = "li" + self.output_layer = "m2" + self.output_layer_pitch = self.m2_pitch + else: + self.bus_layer = "m2" + self.bus_directions = None + self.bus_pitch = self.m2_pitch + self.bus_space = self.m2_space + # This requires a special jog to ensure to conflicts with the output layers + self.input_layer = "m1" + self.output_layer = "m1" + self.output_layer_pitch = self.m1_pitch + 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.m2_space + # +1 input for spacing for supply rail contacts + self.x_off_inv_1 = (self.number_of_inputs + 1) * self.bus_pitch + self.bus_pitch - # 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 + # x offset to AND decoder includes the left rails, mid rails and inverters, plus two extra bus pitches + self.x_off_and = self.x_off_inv_1 + self.inv.width + (2 * self.number_of_inputs + 2) * self.bus_pitch # x offset to output inverters self.width = self.x_off_and + self.and_mod.width @@ -79,28 +113,30 @@ 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.m3_pitch) - self.input_rails = self.create_vertical_pin_bus(layer="m2", - offset=offset, - names=input_names, - length=self.height - 2 * self.m1_pitch) + # Offsets for the perimeter spacing to other modules + # This uses m3 pitch to leave space for power routes + offset = vector(self.bus_pitch, self.bus_pitch) + self.input_rails = self.create_vertical_bus(layer=self.bus_layer, + offset=offset, + names=input_names, + length=self.height - 2 * self.bus_pitch) 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.m3_pitch) - self.decode_rails = self.create_vertical_bus(layer="m2", + offset = vector(self.x_off_inv_1 + self.inv.width + self.bus_pitch, self.bus_pitch) + self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, offset=offset, names=decode_names, - length=self.height - 2 * self.m1_pitch) + length=self.height - 2 * self.bus_pitch) def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ - self.in_inst = [] + self.inv_inst = [] for inv_num in range(self.number_of_inputs): name = "pre_inv_{0}".format(inv_num) - self.in_inst.append(self.add_inst(name=name, - mod=self.inv)) + self.inv_inst.append(self.add_inst(name=name, + mod=self.inv)) self.connect_inst(["in_{0}".format(inv_num), "inbar_{0}".format(inv_num), "vdd", "gnd"]) @@ -108,6 +144,7 @@ class hierarchical_predecode(design.design): def place_input_inverters(self): """ Place the input inverters to invert input signals for the decode stage. """ for inv_num in range(self.number_of_inputs): + if (inv_num % 2 == 0): y_off = inv_num * (self.inv.height) mirror = "R0" @@ -115,8 +152,8 @@ class hierarchical_predecode(design.design): y_off = (inv_num + 1) * (self.inv.height) mirror="MX" offset = vector(self.x_off_inv_1, y_off) - self.in_inst[inv_num].place(offset=offset, - mirror=mirror) + self.inv_inst[inv_num].place(offset=offset, + mirror=mirror) def create_and_array(self, connections): """ Create the AND stage for the decodes """ @@ -151,21 +188,30 @@ class hierarchical_predecode(design.design): def route_inputs_to_rails(self): """ Route the uninverted inputs to the second set of rails """ + + top_and_gate = self.and_inst[-1] for num in range(self.number_of_inputs): - # 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.m2_via.width + self.m2_space + if num == 0: + pin = top_and_gate.get_pin("A") + elif num == 1: + pin = top_and_gate.get_pin("B") + elif num == 2: + pin = top_and_gate.get_pin("C") + elif num == 3: + pin = top_and_gate.get_pin("D") + else: + debug.error("Too many inputs for predecoder.", -1) + y_offset = pin.cy() 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_stack_center(from_layer="m1", - to_layer="m2", + self.add_path(self.input_layer, [in_pos, a_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, offset=[self.input_rails[in_pin].x, y_offset]) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, offset=[self.decode_rails[a_pin].x, y_offset]) def route_output_and(self): @@ -188,31 +234,47 @@ class hierarchical_predecode(design.design): for inv_num in range(self.number_of_inputs): out_pin = "Abar_{}".format(inv_num) in_pin = "in_{}".format(inv_num) + + inv_out_pin = self.inv_inst[inv_num].get_pin("Z") + inv_out_pos = inv_out_pin.rc() # 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_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]) + if OPTS.tech_name == "s8": + rail_pos = vector(self.decode_rails[out_pin].x, inv_out_pos.y) + self.add_path(self.output_layer, [inv_out_pos, rail_pos]) + else: + y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch + right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0) + rail_pos = vector(self.decode_rails[out_pin].x, y_offset) + self.add_path(self.output_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) + to_layer=self.output_layer, + offset=inv_out_pos) + self.add_via_stack_center(from_layer=self.output_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) # route input - pin = self.in_inst[inv_num].get_pin("A") - inv_in_pos = pin.lc() + pin = self.inv_inst[inv_num].get_pin("A") + inv_in_pos = pin.center() in_pos = vector(self.input_rails[in_pin].x, inv_in_pos.y) - self.add_path("m1", [in_pos, inv_in_pos]) + self.add_path(self.input_layer, [in_pos, inv_in_pos]) self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.input_layer, offset=inv_in_pos) - self.add_via_stack_center(from_layer="m1", - to_layer="m2", - offset=in_pos) + via=self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=in_pos) + # Create the input pin at this location on the rail + self.add_layout_pin_rect_center(text=in_pin, + layer=self.bus_layer, + offset=in_pos, + height=via.mod.second_layer_height, + width=via.mod.second_layer_width) def route_and_to_rails(self): # This 2D array defines the connection mapping @@ -231,43 +293,67 @@ class hierarchical_predecode(design.design): 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_stack_center(from_layer="m1", - to_layer="m2", - offset=rail_pos) + self.add_path(self.input_layer, [rail_pos, pin_pos]) + self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=rail_pos, + directions=self.bus_directions) if gate_pin == "A": direction = None else: direction = ("H", "H") self.add_via_stack_center(from_layer=pin.layer, - to_layer="m1", + to_layer=self.input_layer, 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. """ - # Find the x offsets for where the vias/pins should be placed - in_xoffset = self.in_inst[0].rx() + self.m1_space - # out_xoffset = self.and_inst[0].cx() + self.m1_space - for num in range(0, self.number_of_outputs): - # this will result in duplicate polygons for rails, but who cares - - # Route both supplies + # In s8, we use hand-made decoder cells with vertical power + if OPTS.tech_name == "s8": 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=and_pin.layer, - offset=supply_offset, - width=self.and_inst[num].rx()) + # This makes a wire from top to bottom for both inv and and gates + for i in [self.inv_inst, self.and_inst]: + bot_pins = i[0].get_pins(n) + top_pins = i[-1].get_pins(n) + for (bot_pin, top_pin) in zip(bot_pins, top_pins): + self.add_rect(layer=bot_pin.layer, + offset=vector(bot_pin.lx(), self.bus_pitch), + width=bot_pin.width(), + height=top_pin.uy() - self.bus_pitch) + # This adds power vias at the top of each cell + # (except the last to keep them inside the boundary) + for i in self.inv_inst[:-1:2] + self.and_inst[:-1:2]: + pins = i.get_pins(n) + for pin in pins: + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + self.add_power_pin(name=n, + loc=pin.uc(), + start_layer=pin.layer) + + # In other techs, we are using standard cell decoder cells with horizontal power + else: + for num in range(0, self.number_of_outputs): - # Add pins in two locations - for xoffset in [in_xoffset]: - pin_pos = vector(xoffset, and_pin.cy()) - self.add_power_pin(name=n, - loc=pin_pos, - start_layer=and_pin.layer) + # Route both supplies + for n in ["vdd", "gnd"]: + and_pins = self.and_inst[num].get_pins(n) + for and_pin in and_pins: + self.add_segment_center(layer=and_pin.layer, + start=vector(0, and_pin.cy()), + end=vector(self.width, and_pin.cy())) + + # Add pins in two locations + for xoffset in [self.inv_inst[0].lx() - self.bus_space, + self.and_inst[0].lx() - self.bus_space]: + pin_pos = vector(xoffset, and_pin.cy()) + self.add_power_pin(name=n, + loc=pin_pos, + start_layer=and_pin.layer) diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index b1aacc5a..9c7ddfa3 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode2x4(hierarchical_predecode): """ Pre 2x4 decoder used in hierarchical_decoder. @@ -33,21 +30,6 @@ class hierarchical_predecode2x4(hierarchical_predecode): ["in_0", "in_1", "out_3", "vdd", "gnd"]] self.create_and_array(connections) - def create_layout(self): - """ The general organization is from left to right: - 1) a set of M2 rails for input signals - 2) a set of inverters to invert input signals - 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs - 4) a set of AND gates for inversion - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_and_array() - self.route() - self.add_boundary() - self.DRC_LVS() - def get_and_input_line_combination(self): """ These are the decoder connections of the AND gates to the A,B pins """ combination = [["Abar_0", "Abar_1"], diff --git a/compiler/modules/hierarchical_predecode3x8.py b/compiler/modules/hierarchical_predecode3x8.py index 4f2294f1..e8c44e48 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -5,13 +5,10 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from tech import drc -import debug -import design -from vector import vector from hierarchical_predecode import hierarchical_predecode from globals import OPTS + class hierarchical_predecode3x8(hierarchical_predecode): """ Pre 3x8 decoder used in hierarchical_decoder. @@ -37,22 +34,6 @@ class hierarchical_predecode3x8(hierarchical_predecode): ["in_0", "in_1", "in_2", "out_7", "vdd", "gnd"]] self.create_and_array(connections) - def create_layout(self): - """ - The general organization is from left to right: - 1) a set of M2 rails for input signals - 2) a set of inverters to invert input signals - 3) a set of M2 rails for the vdd, gnd, inverted inputs, inputs - 4) a set of NAND gates for inversion - """ - self.setup_layout_constraints() - self.route_rails() - self.place_input_inverters() - self.place_and_array() - self.route() - self.add_boundary() - self.DRC_LVS() - def get_and_input_line_combination(self): """ These are the decoder connections of the NAND gates to the A,B,C pins """ combination = [["Abar_0", "Abar_1", "Abar_2"], diff --git a/compiler/modules/hierarchical_predecode4x16.py b/compiler/modules/hierarchical_predecode4x16.py new file mode 100644 index 00000000..4a258bfb --- /dev/null +++ b/compiler/modules/hierarchical_predecode4x16.py @@ -0,0 +1,64 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from hierarchical_predecode import hierarchical_predecode +from globals import OPTS + + +class hierarchical_predecode4x16(hierarchical_predecode): + """ + Pre 4x16 decoder used in hierarchical_decoder. + """ + def __init__(self, name, height=None): + hierarchical_predecode.__init__(self, name, 4, height) + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.add_modules() + self.create_input_inverters() + connections=[["inbar_0", "inbar_1", "inbar_2", "inbar_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "inbar_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "inbar_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "inbar_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "inbar_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "inbar_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "inbar_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "inbar_3", "out_7", "vdd", "gnd"], + ["inbar_0", "inbar_1", "inbar_2", "in_3", "out_0", "vdd", "gnd"], + ["in_0", "inbar_1", "inbar_2", "in_3", "out_1", "vdd", "gnd"], + ["inbar_0", "in_1", "inbar_2", "in_3", "out_2", "vdd", "gnd"], + ["in_0", "in_1", "inbar_2", "in_3", "out_3", "vdd", "gnd"], + ["inbar_0", "inbar_1", "in_2", "in_3", "out_4", "vdd", "gnd"], + ["in_0", "inbar_1", "in_2", "in_3", "out_5", "vdd", "gnd"], + ["inbar_0", "in_1", "in_2", "in_3", "out_6", "vdd", "gnd"], + ["in_0", "in_1", "in_2", "in_3", "out_7", "vdd", "gnd"] ] + + self.create_and_array(connections) + + def get_and_input_line_combination(self): + """ These are the decoder connections of the AND gates to the A,B pins """ + combination = [["Abar_0", "Abar_1", "Abar_2", "Abar_3"], + ["A_0", "Abar_1", "Abar_2", "Abar_3"], + ["Abar_0", "A_1", "Abar_2", "Abar_3"], + ["A_0", "A_1", "Abar_2", "Abar_3"], + ["Abar_0", "Abar_1", "A_2" , "Abar_3"], + ["A_0", "Abar_1", "A_2" , "Abar_3"], + ["Abar_0", "A_1", "A_2" , "Abar_3"], + ["A_0", "A_1", "A_2" , "Abar_3"], + ["Abar_0", "Abar_1", "Abar_2", "A_3"], + ["A_0", "Abar_1", "Abar_2", "A_3"], + ["Abar_0", "A_1", "Abar_2", "A_3"], + ["A_0", "A_1", "Abar_2", "A_3"], + ["Abar_0", "Abar_1", "A_2", "A_3"], + ["A_0", "Abar_1", "A_2", "A_3"], + ["Abar_0", "A_1", "A_2", "A_3"], + ["A_0", "A_1", "A_2", "A_3"]] + return combination diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index a27894ae..6293a79d 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -8,7 +8,7 @@ import debug import design from sram_factory import factory from vector import vector - +from tech import layer from globals import OPTS @@ -41,6 +41,10 @@ class port_address(design.design): self.create_wordline_driver() def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.place_instances() self.route_layout() self.DRC_LVS() @@ -85,11 +89,19 @@ class port_address(design.design): def route_internal(self): for row in range(self.num_rows): # The pre/post is to access the pin from "outside" the cell to avoid DRCs - decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc() - driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc() - mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0) - mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1) - self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) + decoder_out_pin = self.row_decoder_inst.get_pin("decode_{}".format(row)) + decoder_out_pos = decoder_out_pin.rc() + driver_in_pin = self.wordline_driver_inst.get_pin("in_{}".format(row)) + driver_in_pos = driver_in_pin.lc() + self.add_zjog(self.route_layer, decoder_out_pos, driver_in_pos) + + self.add_via_stack_center(from_layer=decoder_out_pin.layer, + to_layer=self.route_layer, + offset=decoder_out_pos) + + self.add_via_stack_center(from_layer=driver_in_pin.layer, + to_layer=self.route_layer, + offset=driver_in_pos) def add_modules(self): @@ -97,7 +109,7 @@ class port_address(design.design): num_outputs=self.num_rows) self.add_mod(self.row_decoder) - self.wordline_driver = factory.create(module_type="wordline_driver", + self.wordline_driver = factory.create(module_type="wordline_driver_array", rows=self.num_rows, cols=self.num_cols) self.add_mod(self.wordline_driver) @@ -139,7 +151,6 @@ class port_address(design.design): 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 91e20a45..8cc4e272 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -469,45 +469,37 @@ class port_data(design.design): bank_wmask_name = "bank_wmask_{}".format(bit) self.copy_layout_pin(self.write_mask_and_array_inst, wmask_in_name, bank_wmask_name) - def route_write_mask_and_array_to_write_driver(self,port): - """ Routing of wdriver_sel_{} between write mask AND array and write driver array. Adds layout pin for write - mask AND array output and via for write driver enable """ + def route_write_mask_and_array_to_write_driver(self, port): + """ + Routing of wdriver_sel_{} between write mask AND array and + write driver array. Adds layout pin for write + mask AND array output and via for write driver enable + """ - inst1 = self.write_mask_and_array_inst - inst2 = self.write_driver_array_inst + wmask_inst = self.write_mask_and_array_inst + wdriver_inst = self.write_driver_array_inst - loc = 0 for bit in range(self.num_wmasks): # Bring write mask AND array output pin to port data level - self.copy_layout_pin(inst1, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) + self.copy_layout_pin(wmask_inst, "wmask_out_{0}".format(bit), "wdriver_sel_{0}".format(bit)) - wmask_out_pin = inst1.get_pin("wmask_out_{0}".format(bit)) - wdriver_en_pin = inst2.get_pin("en_{0}".format(bit)) + wmask_out_pin = wmask_inst.get_pin("wmask_out_{0}".format(bit)) + wdriver_en_pin = wdriver_inst.get_pin("en_{0}".format(bit)) + + wmask_pos = wmask_out_pin.center() + wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0) + mid_pos = vector(wdriver_pos.x, wmask_pos.y) - # The metal2 wdriver_sel_{} wire must hit the en_{} pin after the closest bitline pin that's right of the - # the wdriver_sel_{} pin in the write driver AND array. - if bit == 0: - # When the write mask output pin is right of the bitline, the target is found - while (wmask_out_pin.lx() + self.m2_pitch > inst2.get_pin("data_{0}".format(loc)).rx()): - loc += 1 - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - debug.check(loc<=self.num_wmasks, - "Couldn't route the write mask select.") - else: - # Stride by the write size rather than finding the next pin to the right - loc += self.write_size - length = inst2.get_pin("data_{0}".format(loc)).rx() + self.m2_pitch - - beg_pos = wmask_out_pin.center() - middle_pos = vector(length, wmask_out_pin.cy()) - end_pos = vector(length, wdriver_en_pin.cy()) + # Add driver on mask output + self.add_via_center(layers=self.m1_stack, + offset=wmask_pos) # Add via for the write driver array's enable input self.add_via_center(layers=self.m1_stack, - offset=end_pos) + offset=wdriver_pos) # Route between write mask AND array and write driver array - self.add_wire(self.m1_stack, [beg_pos, middle_pos, end_pos]) + self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos]) def route_column_mux_to_precharge_array(self, port): """ Routing of BL and BR between col mux and precharge array """ @@ -516,15 +508,12 @@ class port_data(design.design): if self.col_addr_size==0: return - inst1 = self.column_mux_array_inst - inst2 = self.precharge_array_inst + start_bit = 1 if self.port == 0 else 0 - insn2_start_bit = 1 if self.port == 0 else 0 - - self.connect_bitlines(inst1=inst1, - inst2=inst2, + self.connect_bitlines(inst1=self.column_mux_array_inst, + inst2=self.precharge_array_inst, num_bits=self.num_cols, - inst2_start_bit=insn2_start_bit) + inst2_start_bit=start_bit) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ diff --git a/compiler/modules/wordline_driver.py b/compiler/modules/wordline_driver_array.py similarity index 50% rename from compiler/modules/wordline_driver.py rename to compiler/modules/wordline_driver_array.py index 55f5e707..e3d7c4f9 100644 --- a/compiler/modules/wordline_driver.py +++ b/compiler/modules/wordline_driver_array.py @@ -7,15 +7,12 @@ # import debug import design -import math -from tech import drc +from tech import drc, layer from vector import vector from sram_factory import factory from globals import OPTS -from tech import cell_properties - -class wordline_driver(design.design): +class wordline_driver_array(design.design): """ Creates a Wordline Driver Generates the wordline-driver to drive the bitcell @@ -26,21 +23,9 @@ class wordline_driver(design.design): debug.info(1, "Creating {0}".format(self.name)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) - self.bitcell_rows = rows - self.bitcell_cols = cols + self.rows = rows + self.cols = cols - 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 - - # 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() @@ -51,7 +36,10 @@ class wordline_driver(design.design): self.create_drivers() def create_layout(self): - self.setup_layout_constants() + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.place_drivers() self.route_layout() self.route_vdd_gnd() @@ -61,104 +49,99 @@ class wordline_driver(design.design): def add_pins(self): # inputs to wordline_driver. - for i in range(self.bitcell_rows): + for i in range(self.rows): self.add_pin("in_{0}".format(i), "INPUT") # Outputs from wordline_driver. - for i in range(self.bitcell_rows): + for i in range(self.rows): self.add_pin("wl_{0}".format(i), "OUTPUT") self.add_pin("en", "INPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") def add_modules(self): - self.and2 = factory.create(module_type="pand2", - height=self.cell_height, - size=self.bitcell_cols) - self.add_mod(self.and2) + self.wl_driver = factory.create(module_type="wordline_driver", + size=self.cols) + self.add_mod(self.wl_driver) def route_vdd_gnd(self): """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ - - # Find the x offsets for where the vias/pins should be placed - xoffset_list = [self.and_inst[0].lx()] - 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 - (gate_offset, y_dir) = self.get_gate_offset(0, - self.and2.height, - num) - # Route both supplies + if OPTS.tech_name == "s8": for name in ["vdd", "gnd"]: - supply_pin = self.and_inst[num].get_pin(name) - - # Add pins in two locations - for xoffset in xoffset_list: - pin_pos = vector(xoffset, supply_pin.cy()) - self.add_power_pin(name, pin_pos) + supply_pins = self.wld_inst[0].get_pins(name) + for pin in supply_pins: + self.add_layout_pin_segment_center(text=name, + layer=pin.layer, + start=pin.bc(), + end=vector(pin.cx(), self.height)) + else: + # Find the x offsets for where the vias/pins should be placed + xoffset_list = [self.wld_inst[0].rx()] + for num in range(self.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 + (gate_offset, y_dir) = self.get_gate_offset(0, + self.wl_driver.height, + num) + # Route both supplies + for name in ["vdd", "gnd"]: + supply_pin = self.wld_inst[num].get_pin(name) + + # Add pins in two locations + for xoffset in xoffset_list: + pin_pos = vector(xoffset, supply_pin.cy()) + self.add_power_pin(name, pin_pos) def create_drivers(self): - self.and_inst = [] - for row in range(self.bitcell_rows): + self.wld_inst = [] + for row in range(self.rows): name_and = "wl_driver_and{}".format(row) # add and2 - self.and_inst.append(self.add_inst(name=name_and, - mod=self.and2)) + self.wld_inst.append(self.add_inst(name=name_and, + mod=self.wl_driver)) 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.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.bitcell_rows / self.driver_rows) - def place_drivers(self): + for row in range(self.rows): + if (row % 2): + y_offset = self.wl_driver.height * (row + 1) + inst_mirror = "MX" + else: + y_offset = self.wl_driver.height * row + inst_mirror = "R0" + + and2_offset = [self.wl_driver.width, y_offset] + + # add and2 + self.wld_inst[row].place(offset=and2_offset, + mirror=inst_mirror) + # 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 = self.decoders_per_row * self.and2.width + well_gap - self.height = self.and2.height * self.driver_rows - - 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) - inst_mirror = "MX" - else: - y_offset = self.and2.height * row - inst_mirror = "R0" - - x_offset = dec * self.and2.width - and2_offset = [x_offset, y_offset] - - # add and2 - self.and_inst[inst_index].place(offset=and2_offset, - mirror=inst_mirror) + self.width = self.wl_driver.width + well_gap + self.height = self.wl_driver.height * self.rows def route_layout(self): """ Route all of the signals """ # Wordline enable connection - en_pin = self.and_inst[0].get_pin("B") + en_pin = self.wld_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_bottom_pos, height=self.height) - for inst_index in range(self.bitcell_rows): - and_inst = self.and_inst[inst_index] - row = math.floor(inst_index / self.decoders_per_row) + for row in range(self.rows): + and_inst = self.wld_inst[row] # Drop a via b_pin = and_inst.get_pin("B") @@ -167,18 +150,12 @@ class wordline_driver(design.design): 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=vector(0, a_pos.y), - end=a_pos) + self.copy_layout_pin(and_inst, "A", "in_{0}".format(row)) # output each WL on the right wl_offset = and_inst.get_pin("Z").rc() self.add_layout_pin_segment_center(text="wl_{0}".format(row), - layer="m1", + layer=self.route_layer, start=wl_offset, end=wl_offset - vector(self.m1_width, 0)) @@ -189,7 +166,7 @@ class wordline_driver(design.design): """ stage_effort_list = [] - stage1 = self.and2.get_stage_effort(external_cout, inp_is_rise) + stage1 = self.wl_driver.get_stage_effort(external_cout, inp_is_rise) stage_effort_list.append(stage1) return stage_effort_list @@ -200,5 +177,5 @@ class wordline_driver(design.design): the enable connections in the bank """ # The enable is connected to a and2 for every row. - total_cin = self.and2.get_cin() * self.rows + total_cin = self.wl_driver.get_cin() * self.rows return total_cin diff --git a/compiler/options.py b/compiler/options.py index d891ebd9..1c48d63d 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -134,6 +134,10 @@ class options(optparse.Values): dff_array = "dff_array" dff = "dff" dummy_bitcell = "dummy_bitcell" + inv_dec = "pinv" + nand2_dec = "pnand2" + nand3_dec = "pnand3" + nand4_dec = "pnand4" # Not available right now precharge_array = "precharge_array" ptx = "ptx" replica_bitcell = "replica_bitcell" diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 41189876..435ace1f 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -13,7 +13,7 @@ from sram_factory import factory class pand2(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is an AND (or NAND) with configurable drive strength. """ def __init__(self, name, size=1, height=None, vertical=False, add_wells=True): debug.info(1, "Creating pand2 {}".format(name)) diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 266400c9..578a11c4 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -17,13 +17,13 @@ class pdriver(pgate.pgate): sized for driving a load. """ - def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None, add_wells=True): + def __init__(self, name, inverting=False, fanout=0, size_list=None, height=None, add_wells=True): debug.info(1, "creating pdriver {}".format(name)) self.stage_effort = 3 self.height = height - self.neg_polarity = neg_polarity + self.inverting = inverting self.size_list = size_list self.fanout = fanout @@ -31,8 +31,8 @@ class pdriver(pgate.pgate): debug.error("Either fanout or size list must be specified.", -1) if self.size_list and self.fanout != 0: debug.error("Cannot specify both size_list and fanout.", -1) - if self.size_list and self.neg_polarity: - debug.error("Cannot specify both size_list and neg_polarity.", -1) + if self.size_list and self.inverting: + debug.error("Cannot specify both size_list and inverting.", -1) # Creates the netlist and layout pgate.pgate.__init__(self, name, height, add_wells) @@ -47,9 +47,9 @@ class pdriver(pgate.pgate): int(round(self.fanout ** (1 / self.stage_effort)))) # Increase the number of stages if we need to fix polarity - if self.neg_polarity and (self.num_stages % 2 == 0): + if self.inverting and (self.num_stages % 2 == 0): self.num_stages += 1 - elif not self.neg_polarity and (self.num_stages % 2): + elif not self.inverting and (self.num_stages % 2): self.num_stages += 1 self.size_list = [] diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index cdb89fcb..a8c45641 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -44,7 +44,6 @@ class pgate(design.design): 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 diff --git a/compiler/pgates/pinv_dec.py b/compiler/pgates/pinv_dec.py new file mode 100644 index 00000000..efc21074 --- /dev/null +++ b/compiler/pgates/pinv_dec.py @@ -0,0 +1,217 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import contact +import pinv +import debug +from tech import drc, parameter +from vector import vector +from globals import OPTS +from sram_factory import factory + +if(OPTS.tech_name == "s8"): + from tech import nmos_bins, pmos_bins, accuracy_requirement + + +class pinv_dec(pinv.pinv): + """ + This is another version of pinv but with layout for the decoder. + Other stuff is the same (netlist, sizes, etc.) + """ + + def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True): + + debug.info(2, + "creating pinv_dec structure {0} with size of {1}".format(name, + size)) + if not height: + b = factory.create(module_type="bitcell") + self.cell_height = b.height + else: + self.cell_height = height + + # Inputs to cells are on input layer + # Outputs from cells are on output layer + if OPTS.tech_name == "s8": + self.supply_layer = "m1" + else: + self.supply_layer = "m2" + + pinv.pinv.__init__(self, name, size, beta, self.cell_height, add_wells) + + def determine_tx_mults(self): + """ + Determines the number of fingers needed to achieve the size within + the height constraint. This may fail if the user has a tight height. + """ + + # This is always 1 tx, because we have horizontal transistors. + self.tx_mults = 1 + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + if OPTS.tech_name == "s8": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) + return + + # Over-ride the route input gate to call the horizontal version. + # Other top-level netlist and layout functions are not changed. + 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 is actually ignored and is left to be compatible with the pinv. + """ + + nmos_gate_pin = nmos_inst.get_pin("G") + pmos_gate_pin = pmos_inst.get_pin("G") + + # Check if the gates are aligned and give an error if they aren't! + if nmos_gate_pin.ll().y != pmos_gate_pin.ll().y: + self.gds_write("unaliged_gates.gds") + debug.check(nmos_gate_pin.ll().y == pmos_gate_pin.ll().y, + "Connecting unaligned gates not supported. See unaligned_gates.gds.") + + # Pick point on the left of NMOS and up to PMOS + nmos_gate_pos = nmos_gate_pin.rc() + pmos_gate_pos = pmos_gate_pin.lc() + self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) + + # Center is completely symmetric. + contact_width = contact.poly_contact.width + contact_offset = nmos_gate_pin.lc() \ + - vector(self.poly_extend_active + 0.5 * contact_width, 0) + via = self.add_via_stack_center(from_layer="poly", + to_layer=self.route_layer, + offset=contact_offset, + directions=directions) + self.add_path("poly", [contact_offset, nmos_gate_pin.lc()]) + + self.add_layout_pin_rect_center(text=name, + layer=self.route_layer, + offset=contact_offset, + width=via.mod.second_layer_width, + height=via.mod.second_layer_height) + + def determine_width(self): + self.width = self.pmos_inst.rx() + self.well_extend_active + + def extend_wells(self): + """ Extend bottom to top for each well. """ + + from tech import layer + if "pwell" in layer: + ll = self.nmos_inst.ll() - self.nmos_inst.mod.active_offset + ur = self.nmos_inst.ur() + self.nmos_inst.mod.active_offset + self.add_rect(layer="pwell", + offset=ll, + width=ur.x - ll.x, + height=self.height - ll.y) + + if "nwell" in layer: + ll = self.pmos_inst.ll() - self.pmos_inst.mod.active_offset + ur = self.pmos_inst.ur() + self.pmos_inst.mod.active_offset + self.add_rect(layer="nwell", + offset=ll - vector(self.nwell_enclose_active, 0), + width=ur.x - ll.x + self.nwell_enclose_active, + height=self.height - ll.y + 2 * self.nwell_enclose_active) + + def place_ptx(self): + """ + """ + + # offset so that the input contact is over from the left edge by poly spacing + x_offset = self.nmos.active_offset.y + contact.poly_contact.width + self.poly_space + # center the transistor in the y-dimension + y_offset = self.nmos.width + self.active_space + self.nmos_pos = vector(x_offset, y_offset) + self.nmos_inst.place(self.nmos_pos) + self.nmos_inst.place(self.nmos_pos, + rotate=270) + # place PMOS so it is half a poly spacing down from the top + xoffset = self.nmos_inst.height + 2 * self.poly_extend_active + 2 * self.well_extend_active + drc("pwell_to_nwell") + self.pmos_pos = self.nmos_pos + vector(xoffset, 0) + self.pmos_inst.place(self.pmos_pos, + rotate=270) + + # Output position will be in between the PMOS and NMOS drains + pmos_drain_pos = self.pmos_inst.get_pin("D").center() + nmos_drain_pos = self.nmos_inst.get_pin("D").center() + self.output_pos = vector(0.5 * (pmos_drain_pos.x + nmos_drain_pos.x), nmos_drain_pos.y) + + def route_outputs(self): + """ + Route the output (drains) together. + Optionally, routes output to edge. + """ + + # Get the drain pin + nmos_drain_pin = self.nmos_inst.get_pin("D") + + # Pick point at right most of NMOS and connect over to PMOS + nmos_drain_pos = nmos_drain_pin.lc() + right_side = vector(self.width, nmos_drain_pos.y) + + self.add_layout_pin_segment_center("Z", + self.route_layer, + nmos_drain_pos, + right_side) + + def add_well_contacts(self): + """ Add n/p well taps to the layout and connect to supplies """ + + source_pos = self.pmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.nwell_contact = self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + source_pos = self.nmos_inst.get_pin("S").center() + contact_pos = vector(source_pos.x, self.height) + self.pwell_contact= self.add_via_center(layers=self.active_stack, + offset=contact_pos, + implant_type="p", + well_type="p") + self.add_via_stack_center(offset=contact_pos, + from_layer=self.active_stack[2], + to_layer=self.supply_layer) + + def route_supply_rails(self): + pin = self.nmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("gnd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + pin = self.pmos_inst.get_pin("S") + source_pos = pin.center() + bottom_pos = source_pos.scale(1, 0) + top_pos = bottom_pos + vector(0, self.height) + self.add_layout_pin_segment_center("vdd", + self.supply_layer, + start=bottom_pos, + end=top_pos) + + def connect_rails(self): + """ Connect the nmos and pmos to its respective power rails """ + + source_pos = self.nmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + + source_pos = self.pmos_inst.get_pin("S").center() + self.add_via_stack_center(offset=source_pos, + from_layer=self.route_layer, + to_layer=self.supply_layer) + diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 85b3f0b6..6cbd7cca 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -212,7 +212,10 @@ class pnand3(pgate.pgate): 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 + # This is a more compact offset, but the bottom one works better in the decoders to "center" the pins + # in the height of the gates self.inputA_yoffset = self.output_yoffset - 0.5 * self.route_layer_width - self.route_layer_space + # self.inputA_yoffset = self.output_yoffset - self.m1_pitch self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index c584e70b..f6716a1f 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -386,10 +386,12 @@ class ptx(design.design): well_ll = center_pos - vector(0.5 * self.well_width, 0.5 * self.well_height) if well_name in layer: - self.add_rect(layer=well_name, - offset=well_ll, - width=self.well_width, - height=self.well_height) + well = self.add_rect(layer=well_name, + offset=well_ll, + width=self.well_width, + height=self.well_height) + setattr(self, well_name, well) + if "vtg" in layer: self.add_rect(layer="vtg", offset=well_ll, diff --git a/compiler/pgates/wordline_driver.py b/compiler/pgates/wordline_driver.py new file mode 100644 index 00000000..1b035e20 --- /dev/null +++ b/compiler/pgates/wordline_driver.py @@ -0,0 +1,155 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import debug +from vector import vector +import design +from sram_factory import factory +from globals import OPTS +from tech import layer + + +class wordline_driver(design.design): + """ + This is an AND (or NAND) with configurable drive strength to drive the wordlines. + It is matched to the bitcell height. + """ + def __init__(self, name, size=1, height=None): + debug.info(1, "Creating wordline_driver {}".format(name)) + self.add_comment("size: {}".format(size)) + design.design.__init__(self, name) + + if height is None: + b = factory.create(module_type="bitcell") + self.height = b.height + else: + self.height = height + self.size = size + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.create_modules() + self.create_insts() + + def create_modules(self): + if OPTS.tech_name == "s8": + self.nand = factory.create(module_type="nand2_dec") + self.height = self.nand.height + else: + self.nand = factory.create(module_type="nand2_dec", + height=self.height) + + self.driver = factory.create(module_type="inv_dec", + size=self.size, + height=self.nand.height) + + self.add_mod(self.nand) + self.add_mod(self.driver) + + def create_layout(self): + self.width = self.nand.width + self.driver.width + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + + self.place_insts() + self.route_wires() + self.add_layout_pins() + self.route_supply_rails() + self.add_boundary() + self.DRC_LVS() + + def add_pins(self): + self.add_pin("A", "INPUT") + self.add_pin("B", "INPUT") + self.add_pin("Z", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") + + def create_insts(self): + self.nand_inst = self.add_inst(name="wld_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + + self.driver_inst = self.add_inst(name="wl_driver", + mod=self.driver) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + + def place_insts(self): + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) + + # Add INV to the right + self.driver_inst.place(offset=vector(self.nand_inst.rx(), 0)) + + def route_supply_rails(self): + """ Add vdd/gnd rails to the top, (middle), and bottom. """ + if OPTS.tech_name == "s8": + for name in ["vdd", "gnd"]: + for inst in [self.nand_inst, self.driver_inst]: + self.copy_layout_pin(inst, name) + else: + self.add_layout_pin_rect_center(text="gnd", + layer=self.route_layer, + offset=vector(0.5 * self.width, 0), + width=self.width) + + y_offset = self.height + self.add_layout_pin_rect_center(text="vdd", + layer=self.route_layer, + offset=vector(0.5 * self.width, y_offset), + width=self.width) + + def route_wires(self): + + # nand Z to inv A + z1_pin = self.nand_inst.get_pin("Z") + a2_pin = self.driver_inst.get_pin("A") + if OPTS.tech_name == "s8": + mid1_point = vector(a2_pin.cx(), z1_pin.cy()) + else: + mid1_point = vector(z1_pin.cx(), a2_pin.cy()) + self.add_path(self.route_layer, + [z1_pin.center(), mid1_point, a2_pin.center()]) + + def add_layout_pins(self): + pin = self.driver_inst.get_pin("Z") + self.add_layout_pin_rect_center(text="Z", + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + for pin_name in ["A", "B"]: + pin = self.nand_inst.get_pin(pin_name) + self.add_layout_pin_rect_center(text=pin_name, + layer=pin.layer, + offset=pin.center(), + width=pin.width(), + height=pin.height()) + + def get_stage_efforts(self, external_cout, inp_is_rise=False): + """Get the stage efforts of the A or B -> Z path""" + stage_effort_list = [] + stage1_cout = self.driver.get_cin() + stage1 = self.nand.get_stage_effort(stage1_cout, inp_is_rise) + stage_effort_list.append(stage1) + + stage2 = self.driver.get_stage_effort(external_cout, stage1.is_rise) + stage_effort_list.append(stage2) + + return stage_effort_list + + def get_cin(self): + """Return the relative input capacitance of a single input""" + return self.nand.get_cin() + diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 110dbe15..df6b9d99 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -77,7 +77,9 @@ class sram_factory: """ tech_module_type, tm_overridden = self.get_techmodule_type(module_type) user_module_type, um_overridden = self.get_usermodule_type(module_type) - + #print(module_type, tech_module_type, tm_overridden) + #print(module_type, user_module_type, um_overridden) + # overridden user modules have priority if um_overridden: real_module_type = user_module_type diff --git a/compiler/tests/02_library_lvs_test.py b/compiler/tests/02_library_lvs_test.py index 0acc8926..ed15770d 100755 --- a/compiler/tests/02_library_lvs_test.py +++ b/compiler/tests/02_library_lvs_test.py @@ -35,7 +35,7 @@ class library_lvs_test(openram_test): debug.error("Missing GDS file {}".format(gds_name)) if not os.path.isfile(sp_name): lvs_errors += 1 - debug.error("Missing SPICE file {}".format(gds_name)) + debug.error("Missing SPICE file {}".format(sp_name)) drc_errors += verify.run_drc(name, gds_name) lvs_errors += verify.run_lvs(f, gds_name, sp_name) diff --git a/compiler/tests/04_pand2_dec_test.py b/compiler/tests/04_pand2_dec_test.py new file mode 100755 index 00000000..92b80203 --- /dev/null +++ b/compiler/tests/04_pand2_dec_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pand2_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + import pand2_dec + + debug.info(2, "Testing pand2 gate 4x") + a = pand2_dec.pand2_dec(name="pand2x4", size=4) + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pand3_dec_test.py b/compiler/tests/04_pand3_dec_test.py new file mode 100755 index 00000000..78e576ed --- /dev/null +++ b/compiler/tests/04_pand3_dec_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pand3_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + import pand3_dec + + debug.info(2, "Testing pand3 gate 4x") + a = pand3_dec.pand3_dec(name="pand3x4", size=4) + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pand4_dec_test.py b/compiler/tests/04_pand4_dec_test.py new file mode 100755 index 00000000..78e576ed --- /dev/null +++ b/compiler/tests/04_pand4_dec_test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pand3_dec_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + global verify + import verify + + import pand3_dec + + debug.info(2, "Testing pand3 gate 4x") + a = pand3_dec.pand3_dec(name="pand3x4", size=4) + self.local_check(a) + + globals.end_openram() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_pdriver_test.py b/compiler/tests/04_pdriver_test.py index e65b6fad..41af86f6 100755 --- a/compiler/tests/04_pdriver_test.py +++ b/compiler/tests/04_pdriver_test.py @@ -32,13 +32,13 @@ class pdriver_test(openram_test): c = factory.create(module_type="pdriver", fanout = 50) self.local_check(c) - d = factory.create(module_type="pdriver", fanout = 50, neg_polarity = True) + d = factory.create(module_type="pdriver", fanout = 50, inverting = True) self.local_check(d) e = factory.create(module_type="pdriver", fanout = 64) self.local_check(e) - f = factory.create(module_type="pdriver", fanout = 64, neg_polarity = True) + f = factory.create(module_type="pdriver", fanout = 64, inverting = True) self.local_check(f) globals.end_openram() diff --git a/compiler/tests/04_pinv_dec_1x_test.py b/compiler/tests/04_pinv_dec_1x_test.py new file mode 100755 index 00000000..8876ab58 --- /dev/null +++ b/compiler/tests/04_pinv_dec_1x_test.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class pinv_dec_1x_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + debug.info(2, "Checking 1x size decoder inverter") + tx = factory.create(module_type="pinv_dec", size=1) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/08_wordline_driver_test.py b/compiler/tests/04_wordline_driver_test.py similarity index 93% rename from compiler/tests/08_wordline_driver_test.py rename to compiler/tests/04_wordline_driver_test.py index 8a18a59d..ada65db7 100755 --- a/compiler/tests/08_wordline_driver_test.py +++ b/compiler/tests/04_wordline_driver_test.py @@ -25,7 +25,7 @@ class wordline_driver_test(openram_test): # check wordline driver for single port debug.info(2, "Checking driver") - tx = factory.create(module_type="wordline_driver", rows=8, cols=32) + tx = factory.create(module_type="wordline_driver") self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index 07d995f9..7d155b01 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -21,11 +21,17 @@ class hierarchical_decoder_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + # Checks 2x4 and 2-input NAND decoder debug.info(1, "Testing 16 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=16) self.local_check(a) - + # Checks 2x4 and 2-input NAND decoder with non-power-of-two debug.info(1, "Testing 17 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=17) diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index ebb06330..94fd4838 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -21,7 +21,12 @@ class hierarchical_predecode2x4_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # checking hierarchical precode 2x4 for single port + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + debug.info(1, "Testing sample for hierarchy_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index 63acc416..33f94d9d 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -21,7 +21,12 @@ class hierarchical_predecode3x8_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # checking hierarchical precode 3x8 for single port + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + debug.info(1, "Testing sample for hierarchy_predecode3x8") a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode4x16_test.py b/compiler/tests/06_hierarchical_predecode4x16_test.py new file mode 100755 index 00000000..b4ebda38 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode4x16_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +@unittest.skip("SKIPPING hierarchical_predecode4x16_test") +class hierarchical_predecode4x16_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode4x16") + a = factory.create(module_type="hierarchical_predecode4x16") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/08_wordline_driver_pbitcell_test.py b/compiler/tests/08_wordline_driver_array_pbitcell_test.py similarity index 87% rename from compiler/tests/08_wordline_driver_pbitcell_test.py rename to compiler/tests/08_wordline_driver_array_pbitcell_test.py index 3dd5933d..267aaddf 100755 --- a/compiler/tests/08_wordline_driver_pbitcell_test.py +++ b/compiler/tests/08_wordline_driver_array_pbitcell_test.py @@ -15,9 +15,8 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") -class wordline_driver_pbitcell_test(openram_test): +class wordline_driver_array_pbitcell_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -31,7 +30,7 @@ class wordline_driver_pbitcell_test(openram_test): factory.reset() debug.info(2, "Checking driver (multi-port case)") - tx = factory.create(module_type="wordline_driver", rows=8, cols=64) + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=64) self.local_check(tx) globals.end_openram() diff --git a/compiler/tests/08_wordline_driver_array_test.py b/compiler/tests/08_wordline_driver_array_test.py new file mode 100755 index 00000000..3491cc4f --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_1rw_1r_test.py b/compiler/tests/18_port_data_1rw_1r_test.py index 6201de6a..e9e282a5 100755 --- a/compiler/tests/18_port_data_1rw_1r_test.py +++ b/compiler/tests/18_port_data_1rw_1r_test.py @@ -20,10 +20,10 @@ class port_data_1rw_1r_test(openram_test): globals.init_openram(config_file) from sram_config import sram_config - OPTS.bitcell = "bitcell_1w_1r" - OPTS.num_rw_ports = 0 + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 + OPTS.num_w_ports = 0 c = sram_config(word_size=4, num_words=16) diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py index 38a847a9..6cff481b 100755 --- a/compiler/tests/19_single_bank_test.py +++ b/compiler/tests/19_single_bank_test.py @@ -25,28 +25,28 @@ class single_bank_test(openram_test): c = sram_config(word_size=4, num_words=16) - # c.words_per_row=1 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "No column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) - # c.num_words=32 - # c.words_per_row=2 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "Two way column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) - # c.num_words=64 - # c.words_per_row=4 - # factory.reset() - # c.recompute_sizes() - # debug.info(1, "Four way column mux") - # a = factory.create("bank", sram_config=c) - # self.local_check(a) + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) c.word_size=2 c.num_words=128 diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 99d534d0..4cae1a9c 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -60,6 +60,7 @@ class openram_test(unittest.TestCase): #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) debug.warning("DRC failed but LVS passed: {}".format(a.name)) + #self.fail("DRC failed but LVS passed: {}".format(a.name)) elif drc_result != 0: #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) From b00163e4e1e22883ed910826ed7ed5e880cc9088 Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Fri, 29 May 2020 13:50:34 -0700 Subject: [PATCH 15/42] lvs fix for regression tests --- compiler/modules/replica_column.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 2e3d207e..4ea1b7df 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -114,7 +114,6 @@ class replica_column(design.design): mod=self.dummy_cell) self.connect_inst(self.get_bitcell_pins(0, row)) - def place_instances(self): from tech import cell_properties # Flip the mirrors if we have an odd number of replica+dummy rows at the bottom @@ -149,20 +148,30 @@ class replica_column(design.design): self.cell_inst[row].place(offset=offset, mirror=dir_key) - - def add_layout_pins(self): """ Add the layout pins """ for bl_name in self.cell.get_all_bitline_names(): - bl_pin = self.cell_inst[1].get_pin(bl_name) + bl_pin = self.cell_inst[0].get_pin(bl_name) self.add_layout_pin(text=bl_name, layer=bl_pin.layer, offset=bl_pin.ll(), width=bl_pin.width(), height=self.height) - for row in range(1, self.total_size - 1): + try: + end_caps_enabled = cell_properties.bitcell.end_caps + except AttributeError: + end_caps_enabled = False + + if end_caps_enabled: + row_range_max = self.total_size - 1 + row_range_min = 1 + else: + row_range_max = self.total_size + row_range_min = 0 + + for row in range(row_range_min, row_range_max): for wl_name in self.cell.get_all_wl_names(): wl_pin = self.cell_inst[row].get_pin(wl_name) self.add_layout_pin(text="{0}_{1}".format(wl_name,row), @@ -172,7 +181,7 @@ class replica_column(design.design): height=wl_pin.height()) # For every second row and column, add a via for gnd and vdd - for row in range(1, self.total_size - 1): + for row in range(row_range_min, row_range_max): inst = self.cell_inst[row] for pin_name in ["vdd", "gnd"]: self.copy_layout_pin(inst, pin_name) From 82dc937768e2e88fae741600d8c604348fd7093a Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 29 May 2020 16:53:47 -0700 Subject: [PATCH 16/42] Add missing vias by using via stack function --- compiler/modules/port_data.py | 74 ++++++++++++++++----------------- compiler/pgates/precharge.py | 77 +++++++++++++---------------------- 2 files changed, 65 insertions(+), 86 deletions(-) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 8cc4e272..44bb8ed8 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -534,11 +534,14 @@ class port_data(design.design): else: start_bit=0 - self.channel_route_bitlines(inst1=inst1, - inst1_bls_template=inst1_bls_templ, - inst2=inst2, - num_bits=self.word_size, - inst1_start_bit=start_bit) + # This could be a channel route, but in some techs the bitlines + # are too close together. + print("SA to precharge") + self.connect_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) def route_write_driver_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -558,10 +561,13 @@ class port_data(design.design): else: start_bit=0 - self.channel_route_bitlines(inst1=inst1, inst2=inst2, - num_bits=self.word_size, - inst1_bls_template=inst1_bls_templ, - inst1_start_bit=start_bit) + # This could be a channel route, but in some techs the bitlines + # are too close together. + print("WD to precharge") + self.connect_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -569,11 +575,12 @@ class port_data(design.design): inst1 = self.write_driver_array_inst inst2 = self.sense_amp_array_inst - # These should be pitch matched in the cell library, - # but just in case, do a channel route. - self.channel_route_bitlines(inst1=inst1, - inst2=inst2, - num_bits=self.word_size) + # This could be a channel route, but in some techs the bitlines + # are too close together. + print("WD to SA") + self.connect_bitlines(inst1=inst1, + inst2=inst2, + num_bits=self.word_size) def route_bitline_pins(self): """ Add the bitline pins for the given port """ @@ -676,10 +683,9 @@ class port_data(design.design): Route the bl and br of two modules using the channel router. """ - bot_inst_group, top_inst_group = self._group_bitline_instances( - inst1, inst2, num_bits, - inst1_bls_template, inst1_start_bit, - inst2_bls_template, inst2_start_bit) + bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits, + inst1_bls_template, inst1_start_bit, + inst2_bls_template, inst2_start_bit) # 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! @@ -688,13 +694,8 @@ class port_data(design.design): bottom_names = self._get_bitline_pins(bot_inst_group, bit) top_names = self._get_bitline_pins(top_inst_group, bit) - if bottom_names[0].layer == "m2": - bitline_dirs = ("H", "V") - elif bottom_names[0].layer == "m1": - bitline_dirs = ("V", "H") - route_map = list(zip(bottom_names, top_names)) - self.create_horizontal_channel_route(route_map, offset, self.m1_stack, bitline_dirs) + self.create_horizontal_channel_route(route_map, offset, self.m1_stack) def connect_bitlines(self, inst1, inst2, num_bits, inst1_bls_template="{inst}_{bit}", @@ -707,26 +708,23 @@ class port_data(design.design): in the middle between the two modules (if needed). """ - bot_inst_group, top_inst_group = self._group_bitline_instances( - inst1, inst2, num_bits, - inst1_bls_template, inst1_start_bit, - inst2_bls_template, inst2_start_bit) + bot_inst_group, top_inst_group = self._group_bitline_instances(inst1, inst2, num_bits, + inst1_bls_template, inst1_start_bit, + inst2_bls_template, inst2_start_bit) for col in range(num_bits): + print(col) bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col) top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col) bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc() top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc() - - yoffset = 0.5 * (top_bl.y + bot_bl.y) - self.add_path("m2", [bot_bl, - vector(bot_bl.x, yoffset), - vector(top_bl.x, yoffset), - top_bl]) - self.add_path("m2", [bot_br, - vector(bot_br.x, yoffset), - vector(top_br.x, yoffset), - top_br]) + + print("BL", bot_bl, top_bl) + print(bot_bl_pin, top_bl_pin) + print("BR", bot_br, top_br) + print(bot_br_pin, top_br_pin) + self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V") + self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V") def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index d47e445f..ae314452 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -105,21 +105,19 @@ class precharge(design.design): # center of vdd rail pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) - self.add_path("m1", [pmos_pin.uc(), pmos_vdd_pos]) + self.add_path(self.bitline_layer, [pmos_pin.center(), pmos_vdd_pos]) - # if enable is not on M1, the supply can be - if self.en_layer != "m1": - self.add_via_center(layers=self.m1_stack, - offset=pmos_vdd_pos) - + self.add_via_stack_center(from_layer=self.bitline_layer, + to_layer=self.en_layer, + offset=pmos_vdd_pos) self.add_power_pin("vdd", self.well_contact_pos, directions=("V", "V")) - - # Hack for li layers - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=self.well_contact_pos) + + self.add_via_stack_center(from_layer=pmos_pin.layer, + to_layer=self.bitline_layer, + offset=pmos_pin.center(), + directions=("V", "V")) def create_ptx(self): """ @@ -199,14 +197,9 @@ class precharge(design.design): # midway in the 4 M2 tracks offset = self.lower_pmos_inst.get_pin("G").ul() \ + vector(0, 0.5 * self.m2_pitch) - self.add_via_center(layers=self.poly_stack, - offset=offset) - if self.en_layer == "m2": - self.add_via_center(layers=self.m1_stack, - offset=offset) - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=offset) + self.add_via_stack_center(from_layer="poly", + to_layer=self.en_layer, + offset=offset) # adds the en rail on metal1 self.add_layout_pin_segment_center(text="en_bar", @@ -225,13 +218,13 @@ class precharge(design.design): self.nwell_extend_active self.well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) + \ vector(0, offset_height) - self.add_via_center(layers=self.active_stack, - offset=self.well_contact_pos, - implant_type="n", - well_type="n") - if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=self.well_contact_pos) + self.well_contact = self.add_via_center(layers=self.active_stack, + offset=self.well_contact_pos, + implant_type="n", + well_type="n") + self.add_via_stack_center(from_layer=self.active_stack[2], + to_layer=self.bitline_layer, + offset=self.well_contact_pos) self.height = self.well_contact_pos.y + contact.active_contact.height + self.m1_space @@ -288,31 +281,19 @@ class precharge(design.design): Adds contacts/via from metal1 to metal2 for bit-lines """ - # No contacts needed if M1 - if self.bitline_layer == "m1": - return - # BL - lower_pin = self.lower_pmos_inst.get_pin("S") - self.lower_via = self.add_via_center(layers=self.m1_stack, - offset=lower_pin.center(), - directions=("V", "V")) + for lower_pin in [self.lower_pmos_inst.get_pin("S"), self.lower_pmos_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=lower_pin.layer, + to_layer=self.bitline_layer, + offset=lower_pin.center(), + directions=("V", "V")) - lower_pin = self.lower_pmos_inst.get_pin("D") - self.lower_via = self.add_via_center(layers=self.m1_stack, - offset=lower_pin.center(), - directions=("V", "V")) - # BR - upper_pin = self.upper_pmos1_inst.get_pin("S") - self.upper_via2 = self.add_via_center(layers=self.m1_stack, - offset=upper_pin.center(), - directions=("V", "V")) - - upper_pin = self.upper_pmos2_inst.get_pin("D") - self.upper_via2 = self.add_via_center(layers=self.m1_stack, - offset=upper_pin.center(), - directions=("V", "V")) + for upper_pin in [self.upper_pmos1_inst.get_pin("S"), self.upper_pmos2_inst.get_pin("D")]: + self.add_via_stack_center(from_layer=upper_pin.layer, + to_layer=self.bitline_layer, + offset=upper_pin.center(), + directions=("V", "V")) def connect_pmos(self, pmos_pin, bit_xoffset): """ From 496a24389c4e95f780a71a1f73959366a9744f4d Mon Sep 17 00:00:00 2001 From: mrg Date: Fri, 29 May 2020 16:57:47 -0700 Subject: [PATCH 17/42] Remove prints --- compiler/modules/port_data.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 44bb8ed8..d544b6be 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -536,7 +536,6 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - print("SA to precharge") self.connect_bitlines(inst1=inst1, inst1_bls_template=inst1_bls_templ, inst2=inst2, @@ -563,7 +562,6 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - print("WD to precharge") self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, inst1_bls_template=inst1_bls_templ, @@ -577,7 +575,6 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - print("WD to SA") self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size) @@ -713,16 +710,11 @@ class port_data(design.design): inst2_bls_template, inst2_start_bit) for col in range(num_bits): - print(col) bot_bl_pin, bot_br_pin = self._get_bitline_pins(bot_inst_group, col) top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col) bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc() top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc() - print("BL", bot_bl, top_bl) - print(bot_bl_pin, top_bl_pin) - print("BR", bot_br, top_br) - print(bot_br_pin, top_br_pin) self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V") self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V") From b3b03d4d397e82dbeb357ac01f381018226e2b27 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 1 Jun 2020 16:46:00 -0700 Subject: [PATCH 18/42] Hard cells can accept height parameter too. --- compiler/custom/{pand2_dec.py => and2_dec.py} | 33 +++++++++++------- compiler/custom/{pand3_dec.py => and3_dec.py} | 29 ++++++++++------ compiler/custom/{pand4_dec.py => and4_dec.py} | 34 ++++++++++++------- compiler/custom/nand2_dec.py | 10 +++++- compiler/custom/nand3_dec.py | 10 +++++- compiler/custom/nand4_dec.py | 10 +++++- compiler/modules/hierarchical_decoder.py | 19 ++++------- compiler/modules/hierarchical_predecode.py | 6 ++-- compiler/pgates/wordline_driver.py | 8 ++--- ..._pand3_dec_test.py => 04_and2_dec_test.py} | 8 ++--- ..._pand4_dec_test.py => 04_and3_dec_test.py} | 8 ++--- ..._pand2_dec_test.py => 04_and4_dec_test.py} | 8 ++--- 12 files changed, 112 insertions(+), 71 deletions(-) rename compiler/custom/{pand2_dec.py => and2_dec.py} (89%) rename compiler/custom/{pand3_dec.py => and3_dec.py} (90%) rename compiler/custom/{pand4_dec.py => and4_dec.py} (89%) rename compiler/tests/{04_pand3_dec_test.py => 04_and2_dec_test.py} (85%) rename compiler/tests/{04_pand4_dec_test.py => 04_and3_dec_test.py} (85%) rename compiler/tests/{04_pand2_dec_test.py => 04_and4_dec_test.py} (85%) diff --git a/compiler/custom/pand2_dec.py b/compiler/custom/and2_dec.py similarity index 89% rename from compiler/custom/pand2_dec.py rename to compiler/custom/and2_dec.py index 0db490ff..b764bb83 100644 --- a/compiler/custom/pand2_dec.py +++ b/compiler/custom/and2_dec.py @@ -7,34 +7,37 @@ # import debug from vector import vector -import pgate +import design from sram_factory import factory from globals import OPTS +from tech import layer -class pand2_dec(pgate.pgate): +class and2_dec(design.design): """ This is an AND with configurable drive strength. """ def __init__(self, name, size=1, height=None, add_wells=True): - debug.info(1, "Creating pand2_dec {}".format(name)) + + design.design.__init__(self, name) + + debug.info(1, "Creating and2_dec {}".format(name)) self.add_comment("size: {}".format(size)) - self.size = size - - pgate.pgate.__init__(self, name, height, add_wells) - + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + def create_netlist(self): self.add_pins() self.create_modules() self.create_insts() def create_modules(self): - if OPTS.tech_name == "s8": - self.nand = factory.create(module_type="nand2_dec") - else: - self.nand = factory.create(module_type="nand2_dec", - height=self.height) + self.nand = factory.create(module_type="nand2_dec", + height=self.height) self.inv = factory.create(module_type="inv_dec", height=self.height, @@ -44,7 +47,13 @@ class pand2_dec(pgate.pgate): self.add_mod(self.inv) def create_layout(self): + + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" self.width = self.nand.width + self.inv.width + self.height = self.nand.height self.place_insts() self.add_wires() diff --git a/compiler/custom/pand3_dec.py b/compiler/custom/and3_dec.py similarity index 90% rename from compiler/custom/pand3_dec.py rename to compiler/custom/and3_dec.py index 3ec1eb45..89cc84f8 100644 --- a/compiler/custom/pand3_dec.py +++ b/compiler/custom/and3_dec.py @@ -7,22 +7,26 @@ # import debug from vector import vector -import pgate +import design from sram_factory import factory from globals import OPTS +from tech import layer -class pand3_dec(pgate.pgate): +class and3_dec(design.design): """ This is an AND with configurable drive strength. """ def __init__(self, name, size=1, height=None, add_wells=True): - debug.info(1, "Creating pand3_dec {}".format(name)) + design.design.__init__(self, name) + debug.info(1, "Creating and3_dec {}".format(name)) self.add_comment("size: {}".format(size)) - self.size = size + self.height = height - pgate.pgate.__init__(self, name, height, add_wells) + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() def create_netlist(self): self.add_pins() @@ -30,11 +34,8 @@ class pand3_dec(pgate.pgate): self.create_insts() def create_modules(self): - if OPTS.tech_name == "s8": - self.nand = factory.create(module_type="nand3_dec") - else: - self.nand = factory.create(module_type="nand3_dec", - height=self.height) + self.nand = factory.create(module_type="nand3_dec", + height=self.height) self.inv = factory.create(module_type="inv_dec", height=self.height, @@ -44,8 +45,14 @@ class pand3_dec(pgate.pgate): self.add_mod(self.inv) def create_layout(self): - self.width = self.nand.width + self.inv.width + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.width = self.nand.width + self.inv.width + self.height = self.nand.height + self.place_insts() self.add_wires() self.add_layout_pins() diff --git a/compiler/custom/pand4_dec.py b/compiler/custom/and4_dec.py similarity index 89% rename from compiler/custom/pand4_dec.py rename to compiler/custom/and4_dec.py index ee65a349..f99c048f 100644 --- a/compiler/custom/pand4_dec.py +++ b/compiler/custom/and4_dec.py @@ -7,22 +7,28 @@ # import debug from vector import vector -import pgate +import design from sram_factory import factory from globals import OPTS +from tech import layer -class pand4_dec(pgate.pgate): +class and4_dec(design.design): """ This is an AND with configurable drive strength. """ def __init__(self, name, size=1, height=None, add_wells=True): - debug.info(1, "Creating pand4_dec {}".format(name)) - self.add_comment("size: {}".format(size)) - - self.size = size - pgate.pgate.__init__(self, name, height, add_wells) + design.design.__init__(self, name) + + debug.info(1, "Creating and4_dec {}".format(name)) + self.add_comment("size: {}".format(size)) + self.size = size + self.height = height + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() def create_netlist(self): self.add_pins() @@ -30,20 +36,24 @@ class pand4_dec(pgate.pgate): self.create_insts() def create_modules(self): - if OPTS.tech_name == "s8": - self.nand = factory.create(module_type="nand4_dec") - else: - self.nand = factory.create(module_type="nand4_dec", - height=self.height) + self.nand = factory.create(module_type="nand4_dec", + height=self.height) self.inv = factory.create(module_type="inv_dec", + height=self.height, size=self.size) self.add_mod(self.nand) self.add_mod(self.inv) def create_layout(self): + if "li" in layer: + self.route_layer = "li" + else: + self.route_layer = "m1" + self.width = self.nand.width + self.inv.width + self.height = self.nand.height self.place_insts() self.add_wires() diff --git a/compiler/custom/nand2_dec.py b/compiler/custom/nand2_dec.py index 30a018a1..c806bf5a 100644 --- a/compiler/custom/nand2_dec.py +++ b/compiler/custom/nand2_dec.py @@ -6,7 +6,7 @@ # All rights reserved. # import design -from tech import GDS, layer, spice, parameter +from tech import GDS, layer, spice, parameter, drc import logical_effort import utils @@ -31,6 +31,14 @@ class nand2_dec(design.design): self.height = nand2_dec.height self.pin_map = nand2_dec.pin_map self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/custom/nand3_dec.py b/compiler/custom/nand3_dec.py index d700062c..5eea68de 100644 --- a/compiler/custom/nand3_dec.py +++ b/compiler/custom/nand3_dec.py @@ -6,7 +6,7 @@ # All rights reserved. # import design -from tech import GDS, layer, spice, parameter +from tech import GDS, layer, spice, parameter, drc import logical_effort import utils @@ -31,6 +31,14 @@ class nand3_dec(design.design): self.height = nand3_dec.height self.pin_map = nand3_dec.pin_map self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/custom/nand4_dec.py b/compiler/custom/nand4_dec.py index 5c2bb882..df3eee14 100644 --- a/compiler/custom/nand4_dec.py +++ b/compiler/custom/nand4_dec.py @@ -6,7 +6,7 @@ # All rights reserved. # import design -from tech import GDS, layer, spice, parameter +from tech import GDS, layer, spice, parameter, drc import logical_effort import utils @@ -31,6 +31,14 @@ class nand4_dec(design.design): self.height = nand4_dec.height self.pin_map = nand4_dec.pin_map self.add_pin_types(self.type_list) + + # FIXME: For now... + size = 1 + self.size = size + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 3cb5d35c..8b711031 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -57,21 +57,15 @@ class hierarchical_decoder(design.design): self.DRC_LVS() def add_modules(self): - if OPTS.tech_name == "s8": - self.and2 = factory.create(module_type="pand2_dec") - else: - self.and2 = factory.create(module_type="pand2_dec", - height=self.cell_height) + self.and2 = factory.create(module_type="and2_dec", + height=self.cell_height) self.add_mod(self.and2) - if OPTS.tech_name == "s8": - self.and3 = factory.create(module_type="pand3_dec") - else: - self.and3 = factory.create(module_type="pand3_dec", - height=self.cell_height) - + + self.and3 = factory.create(module_type="and3_dec", + height=self.cell_height) self.add_mod(self.and3) # TBD - # self.and4 = factory.create(module_type="pand4_dec") + # self.and4 = factory.create(module_type="and4_dec") # self.add_mod(self.and4) self.add_decoders() @@ -180,6 +174,7 @@ class hierarchical_decoder(design.design): # Two extra pitches between modules on left and right self.internal_routing_width = self.total_number_of_predecoder_outputs * self.bus_pitch + self.bus_pitch self.row_decoder_height = self.and2.height * self.num_outputs + # Extra bus space for supply contacts self.input_routing_width = self.num_inputs * self.bus_pitch + self.bus_space diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index 8244028e..fa44b080 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -42,13 +42,13 @@ class hierarchical_predecode(design.design): # FIXME: Default parms are required for hard cells for now. if self.number_of_inputs == 2: - self.and_mod = factory.create(module_type="pand2_dec", + self.and_mod = factory.create(module_type="and2_dec", height=self.cell_height) elif self.number_of_inputs == 3: - self.and_mod = factory.create(module_type="pand3_dec", + self.and_mod = factory.create(module_type="and3_dec", height=self.cell_height) elif self.number_of_inputs == 4: - self.and_mod = factory.create(module_type="pand4_dec", + self.and_mod = factory.create(module_type="and4_dec", height=self.cell_height) else: debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1) diff --git a/compiler/pgates/wordline_driver.py b/compiler/pgates/wordline_driver.py index 1b035e20..a817941b 100644 --- a/compiler/pgates/wordline_driver.py +++ b/compiler/pgates/wordline_driver.py @@ -40,12 +40,8 @@ class wordline_driver(design.design): self.create_insts() def create_modules(self): - if OPTS.tech_name == "s8": - self.nand = factory.create(module_type="nand2_dec") - self.height = self.nand.height - else: - self.nand = factory.create(module_type="nand2_dec", - height=self.height) + self.nand = factory.create(module_type="nand2_dec", + height=self.height) self.driver = factory.create(module_type="inv_dec", size=self.size, diff --git a/compiler/tests/04_pand3_dec_test.py b/compiler/tests/04_and2_dec_test.py similarity index 85% rename from compiler/tests/04_pand3_dec_test.py rename to compiler/tests/04_and2_dec_test.py index 78e576ed..355d3b15 100755 --- a/compiler/tests/04_pand3_dec_test.py +++ b/compiler/tests/04_and2_dec_test.py @@ -15,7 +15,7 @@ from globals import OPTS from sram_factory import factory import debug -class pand3_dec_test(openram_test): +class and2_dec_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -23,10 +23,10 @@ class pand3_dec_test(openram_test): global verify import verify - import pand3_dec + import and2_dec - debug.info(2, "Testing pand3 gate 4x") - a = pand3_dec.pand3_dec(name="pand3x4", size=4) + debug.info(2, "Testing and2 gate 4x") + a = and2_dec.and2_dec(name="and2x4", size=4) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/04_pand4_dec_test.py b/compiler/tests/04_and3_dec_test.py similarity index 85% rename from compiler/tests/04_pand4_dec_test.py rename to compiler/tests/04_and3_dec_test.py index 78e576ed..7794f36b 100755 --- a/compiler/tests/04_pand4_dec_test.py +++ b/compiler/tests/04_and3_dec_test.py @@ -15,7 +15,7 @@ from globals import OPTS from sram_factory import factory import debug -class pand3_dec_test(openram_test): +class and3_dec_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -23,10 +23,10 @@ class pand3_dec_test(openram_test): global verify import verify - import pand3_dec + import and3_dec - debug.info(2, "Testing pand3 gate 4x") - a = pand3_dec.pand3_dec(name="pand3x4", size=4) + debug.info(2, "Testing and3 gate 4x") + a = and3_dec.and3_dec(name="and3x4", size=4) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/04_pand2_dec_test.py b/compiler/tests/04_and4_dec_test.py similarity index 85% rename from compiler/tests/04_pand2_dec_test.py rename to compiler/tests/04_and4_dec_test.py index 92b80203..7794f36b 100755 --- a/compiler/tests/04_pand2_dec_test.py +++ b/compiler/tests/04_and4_dec_test.py @@ -15,7 +15,7 @@ from globals import OPTS from sram_factory import factory import debug -class pand2_dec_test(openram_test): +class and3_dec_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) @@ -23,10 +23,10 @@ class pand2_dec_test(openram_test): global verify import verify - import pand2_dec + import and3_dec - debug.info(2, "Testing pand2 gate 4x") - a = pand2_dec.pand2_dec(name="pand2x4", size=4) + debug.info(2, "Testing and3 gate 4x") + a = and3_dec.and3_dec(name="and3x4", size=4) self.local_check(a) globals.end_openram() From 9ecf98a4c32c52bde08010d2abb0e8b26ac38054 Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 1 Jun 2020 16:46:22 -0700 Subject: [PATCH 19/42] SRAM factory uses default name for first instance even if it has arguments. --- compiler/sram_factory.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index df6b9d99..46eed3d4 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -111,11 +111,12 @@ class sram_factory: return obj_item # If no prefered module name is provided, we generate one. - if module_name is None: - # Use the default name if there are default arguments + if not module_name: + # Use the default name for the first cell. # This is especially for library cells so that the # spice and gds files can be found. - if len(kwargs) > 0: + # Subsequent objects will get unique names to help with GDS limitation. + if len(self.objects[real_module_type]) > 0: # Create a unique name and increment the index module_name = "{0}_{1}".format(real_module_type, self.module_indices[real_module_type]) From b39579c109848845c753bcef74b87cf30af7ad50 Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Mon, 1 Jun 2020 20:55:15 -0700 Subject: [PATCH 20/42] temp drc fix for regression tests --- compiler/base/hierarchy_layout.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 0da2c4b0..722ac2f1 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1078,15 +1078,18 @@ class layout(): # Route each pin to the trunk for pin in pins: - # If there is sufficient space, Route from the edge of the pins - # Otherwise, route from the center of the pins - if max_y_bc - min_y_uc > pitch: - if pin.center().y == max_y: - mid = vector(trunk_offset.x, pin.bc().y) - else: - mid = vector(trunk_offset.x, pin.uc().y) - else: - mid = vector(trunk_offset.x, pin.center().y) + # This code block currently causes drc violations for the topmost + # port when using multiport, TODO: fix or remove this block + # # If there is sufficient space, Route from the edge of the pins + # # Otherwise, route from the center of the pins + # if max_y_bc - min_y_uc > pitch: + # if pin.center().y == max_y: + # mid = vector(trunk_offset.x, pin.bc().y) + # else: + # mid = vector(trunk_offset.x, pin.uc().y) + # else: + # mid = vector(trunk_offset.x, pin.center().y) + mid = vector(trunk_offset.x, pin.center().y) self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) From b0aa70ffda436e9b22f5f51c249671e2660c0ed6 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 09:23:27 -0700 Subject: [PATCH 21/42] Fix precharge vdd route layer --- compiler/pgates/precharge.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index ae314452..8b84a1b8 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -105,17 +105,14 @@ class precharge(design.design): # center of vdd rail pmos_vdd_pos = vector(pmos_pin.cx(), vdd_position.y) - self.add_path(self.bitline_layer, [pmos_pin.center(), pmos_vdd_pos]) + self.add_path(self.en_layer, [pmos_pin.center(), pmos_vdd_pos]) - self.add_via_stack_center(from_layer=self.bitline_layer, - to_layer=self.en_layer, - offset=pmos_vdd_pos) self.add_power_pin("vdd", self.well_contact_pos, directions=("V", "V")) self.add_via_stack_center(from_layer=pmos_pin.layer, - to_layer=self.bitline_layer, + to_layer=self.en_layer, offset=pmos_pin.center(), directions=("V", "V")) From e69b665689c0b2b29ba0a124b15e6ba505199fca Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 09:31:43 -0700 Subject: [PATCH 22/42] Flatten pbitcell_1 too --- technology/scn4m_subm/mag_lib/setup.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/technology/scn4m_subm/mag_lib/setup.tcl b/technology/scn4m_subm/mag_lib/setup.tcl index 95e7dbea..09bbea27 100644 --- a/technology/scn4m_subm/mag_lib/setup.tcl +++ b/technology/scn4m_subm/mag_lib/setup.tcl @@ -6,7 +6,7 @@ equate class {-circuit1 pfet} {-circuit2 p} flatten class {-circuit1 dummy_cell_6t} flatten class {-circuit1 dummy_cell_1rw_1r} flatten class {-circuit1 dummy_cell_1w_1r} -flatten class {-circuit1 bitcell_array_0} +flatten class {-circuit1 pbitcell} flatten class {-circuit1 pbitcell_0} flatten class {-circuit1 pbitcell_1} property {-circuit1 nfet} remove as ad ps pd From 620604603c9949627df77b5f887f04e334d84e40 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 10:08:37 -0700 Subject: [PATCH 23/42] Fixed offset jogs --- compiler/base/hierarchy_layout.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 4d0345a3..f659aed4 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -450,7 +450,7 @@ class layout(): path=coordinates, layer_widths=layer_widths) - def add_zjog(self, layer, start, end, first_direction="H"): + def add_zjog(self, layer, start, end, first_direction="H", fixed_offset=None): """ Add a simple jog at the halfway point. If layer is a single value, it is a path. @@ -459,11 +459,17 @@ class layout(): # vertical first if first_direction == "V": - mid1 = vector(start.x, 0.5 * start.y + 0.5 * end.y) + if fixed_offset: + mid1 = vector(start.x, fixed_offset) + else: + 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) + if fixed_offset: + mid1 = vector(fixed_offset, start.y) + else: + 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.") From a1c7474f80aaa35060f503b7bcb886e14f88b2c7 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 10:08:53 -0700 Subject: [PATCH 24/42] Revert to channel route of bitlines --- compiler/modules/port_data.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index d544b6be..0ad0dd86 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -536,11 +536,11 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - self.connect_bitlines(inst1=inst1, - inst1_bls_template=inst1_bls_templ, - inst2=inst2, - num_bits=self.word_size, - inst1_start_bit=start_bit) + self.channel_route_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) def route_write_driver_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -562,10 +562,10 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - self.connect_bitlines(inst1=inst1, inst2=inst2, - num_bits=self.word_size, - inst1_bls_template=inst1_bls_templ, - inst1_start_bit=start_bit) + self.channel_route_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -714,9 +714,10 @@ class port_data(design.design): top_bl_pin, top_br_pin = self._get_bitline_pins(top_inst_group, col) bot_bl, bot_br = bot_bl_pin.uc(), bot_br_pin.uc() top_bl, top_br = top_bl_pin.bc(), top_br_pin.bc() - - self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V") - self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V") + + layer_pitch = getattr(self, "{}_pitch".format(top_bl_pin.layer)) + self.add_zjog(bot_bl_pin.layer, bot_bl, top_bl, "V", top_bl_pin.by() - layer_pitch) + self.add_zjog(bot_br_pin.layer, bot_br, top_br, "V", top_bl_pin.by() - 2 * layer_pitch) def graph_exclude_precharge(self): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" From 45b0601e4b0789f84c1d38230d2ee9a890fafe34 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 11:43:31 -0700 Subject: [PATCH 25/42] Fix via directions in s8 col mux --- .../modules/single_level_column_mux_array.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 5dc9cbf8..1514d64a 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -7,7 +7,7 @@ # import design import debug -from tech import layer +from tech import layer, preferred_directions from vector import vector from sram_factory import factory from globals import OPTS @@ -33,10 +33,15 @@ class single_level_column_mux_array(design.design): if "li" in layer: self.col_mux_stack = self.li_stack - self.col_mux_stack_pitch = self.li_pitch + self.col_mux_stack_pitch = self.m1_pitch else: self.col_mux_stack = self.m1_stack self.col_mux_stack_pitch = self.m1_pitch + + if preferred_directions[self.col_mux_stack[0]] == "V": + self.via_directions = ("H", "H") + else: + self.via_directions = "pref" self.create_netlist() if not OPTS.netlist_only: @@ -173,7 +178,8 @@ class single_level_column_mux_array(design.design): self.get_pin("sel_{}".format(sel_index)).cy()) # Add the poly contact with a shift to account for the rotation self.add_via_center(layers=self.poly_stack, - offset=offset) + offset=offset, + directions=self.via_directions) self.add_path("poly", [offset, gate_offset]) def route_bitlines(self): @@ -224,11 +230,13 @@ class single_level_column_mux_array(design.design): # This via is on the right of the wire self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset) + offset=bl_out_offset, + directions=self.via_directions) # This via is on the left of the wire self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset) + offset=br_out_offset, + directions=self.via_directions) else: self.add_path(self.col_mux_stack[2], [bl_out_offset, bl_offset]) @@ -236,10 +244,12 @@ class single_level_column_mux_array(design.design): # This via is on the right of the wire self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset) + offset=bl_out_offset, + directions=self.via_directions) # This via is on the left of the wire self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset) + offset=br_out_offset, + directions=self.via_directions) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" From f1b7b91b1a4958a90b198fce69cd7f21aa9f7ea7 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 11:43:57 -0700 Subject: [PATCH 26/42] Use non-channel route for s8 port_data --- compiler/modules/port_data.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 0ad0dd86..904c82eb 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -180,6 +180,7 @@ class port_data(design.design): # Precharge will be shifted left if needed self.precharge_array = factory.create(module_type="precharge_array", columns=self.num_cols + 1, + port=self.port, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port]) self.add_mod(self.precharge_array) @@ -250,6 +251,7 @@ class port_data(design.design): # module, which happens before we create the real precharge_array self.precharge_array = factory.create(module_type="precharge_array", columns=self.num_cols + 1, + port=self.port, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port]) @@ -536,11 +538,18 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - self.channel_route_bitlines(inst1=inst1, - inst1_bls_template=inst1_bls_templ, - inst2=inst2, - num_bits=self.word_size, - inst1_start_bit=start_bit) + if OPTS.tech_name == "s8": + self.connect_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, + inst1_bls_template=inst1_bls_templ, + inst2=inst2, + num_bits=self.word_size, + inst1_start_bit=start_bit) def route_write_driver_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ @@ -562,11 +571,17 @@ class port_data(design.design): # This could be a channel route, but in some techs the bitlines # are too close together. - self.channel_route_bitlines(inst1=inst1, inst2=inst2, - num_bits=self.word_size, - inst1_bls_template=inst1_bls_templ, - inst1_start_bit=start_bit) - + if OPTS.tech_name == "s8": + self.connect_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + else: + self.channel_route_bitlines(inst1=inst1, inst2=inst2, + num_bits=self.word_size, + inst1_bls_template=inst1_bls_templ, + inst1_start_bit=start_bit) + def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ From fdf51c5a00ca61f8601ae344ee7997be8bd2a5a6 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 11:44:22 -0700 Subject: [PATCH 27/42] Add port option to precharge array --- compiler/modules/precharge_array.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index af612af4..3c64e85f 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -19,13 +19,14 @@ class precharge_array(design.design): of bit line columns, height is the height of the bit-cell array. """ - def __init__(self, name, columns, size=1, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, port, size=1, bitcell_bl="bl", bitcell_br="br"): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} size: {1} bl: {2} br: {3}".format(columns, size, bitcell_bl, bitcell_br)) self.columns = columns self.size = size + self.port = port self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br @@ -106,7 +107,7 @@ class precharge_array(design.design): xoffset = 0 for i in range(self.columns): tempx = xoffset - if cell_properties.bitcell.mirror.y and (i + 1) % 2: + if cell_properties.bitcell.mirror.y and (i + 1 + self.port) % 2: mirror = "MY" tempx = tempx + self.pc_cell.width else: From fce8e878b9ac55d57865ef2e7a4883f901d3daf4 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 13:57:41 -0700 Subject: [PATCH 28/42] Add port to col mux and simplify route with computation to fix mirror bug. --- .../modules/single_level_column_mux_array.py | 86 +++++++------------ .../07_single_level_column_mux_array_test.py | 6 +- 2 files changed, 34 insertions(+), 58 deletions(-) diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 1514d64a..3d80525a 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -20,12 +20,13 @@ class single_level_column_mux_array(design.design): Array of column mux to read the bitlines through the 6T. """ - def __init__(self, name, columns, word_size, bitcell_bl="bl", bitcell_br="br"): + def __init__(self, name, columns, port, word_size, bitcell_bl="bl", bitcell_br="br"): design.design.__init__(self, name) debug.info(1, "Creating {0}".format(self.name)) self.add_comment("cols: {0} word_size: {1} bl: {2} br: {3}".format(columns, word_size, bitcell_bl, bitcell_br)) self.columns = columns + self.port = port self.word_size = word_size self.words_per_row = int(self.columns / self.word_size) self.bitcell_bl = bitcell_bl @@ -117,7 +118,7 @@ class single_level_column_mux_array(design.design): # For every column, add a pass gate for col_num in range(self.columns): xoffset = col_num * self.mux.width - if cell_properties.bitcell.mirror.y and col_num % 2: + if cell_properties.bitcell.mirror.y and (col_num + self.port) % 2: mirror = "MY" xoffset = xoffset + self.mux.width else: @@ -184,72 +185,47 @@ class single_level_column_mux_array(design.design): def route_bitlines(self): """ Connect the output bit-lines to form the appropriate width mux """ - from tech import cell_properties for j in range(self.columns): - bl_offset = self.mux_inst[j].get_pin("bl_out").bc() - br_offset = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset = bl_offset - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) - br_out_offset = br_offset - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) + bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc() + br_offset_begin = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset_end = bl_out_offset + vector(0, self.route_height) - br_out_offset_end = br_out_offset + vector(0, self.route_height) + bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) + br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) - if cell_properties.bitcell.mirror.y and j % 2: - tmp_bl_out_end = br_out_offset_end - tmp_br_out_end = bl_out_offset_end - else: - tmp_bl_out_end = bl_out_offset_end - tmp_br_out_end = br_out_offset_end - - if (j % self.words_per_row) == 0: - # Create the metal1 to connect the n-way mux output from the pass gate - # These will be located below the select lines. Yes, these are M2 width - # to ensure vias are enclosed and M1 min width rules. - width = self.m2_width + self.mux.width * (self.words_per_row - 1) - - if cell_properties.bitcell.mirror.y and (j % 2) == 0: - bl = self.mux.get_pin("bl") - br = self.mux.get_pin("br") - dist = abs(bl.ll().x - br.ll().x) - else: - dist = 0 - - self.add_path(self.col_mux_stack[0], [bl_out_offset, bl_out_offset + vector(width + dist, 0)]) - self.add_path(self.col_mux_stack[0], [br_out_offset, br_out_offset + vector(width - dist, 0)]) + # Add the horizontal wires for the first bit + if j % self.words_per_row == 0: + bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc() + br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc() + bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch) + br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch) + + self.add_path(self.col_mux_stack[0], [bl_out_offset_begin, bl_out_offset_end]) + self.add_path(self.col_mux_stack[0], [br_out_offset_begin, br_out_offset_end]) # Extend the bitline output rails and gnd downward on the first bit of each n-way mux self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)), layer=self.col_mux_stack[2], - start=bl_out_offset, - end=tmp_bl_out_end) + start=bl_offset_begin, + end=bl_out_offset_begin) self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), layer=self.col_mux_stack[2], - start=br_out_offset, - end=tmp_br_out_end) - - # This via is on the right of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset, - directions=self.via_directions) - - # This via is on the left of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset, - directions=self.via_directions) + start=br_offset_begin, + end=br_out_offset_begin) else: - self.add_path(self.col_mux_stack[2], [bl_out_offset, bl_offset]) - self.add_path(self.col_mux_stack[2], [br_out_offset, br_offset]) + self.add_path(self.col_mux_stack[2], [bl_out_offset_begin, bl_offset_begin]) + self.add_path(self.col_mux_stack[2], [br_out_offset_begin, br_offset_begin]) - # This via is on the right of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=bl_out_offset, - directions=self.via_directions) - # This via is on the left of the wire - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_offset, - directions=self.via_directions) + # This via is on the right of the wire + self.add_via_center(layers=self.col_mux_stack, + offset=bl_out_offset_begin, + directions=self.via_directions) + + # This via is on the left of the wire + self.add_via_center(layers=self.col_mux_stack, + offset=br_out_offset_begin, + directions=self.via_directions) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py index c5d48689..fb608f68 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -23,15 +23,15 @@ class single_level_column_mux_test(openram_test): # check single level column mux array in single port debug.info(1, "Testing sample for 2-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8) + a = factory.create(module_type="single_level_column_mux_array", columns=16, port=0, word_size=8) self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4) + a = factory.create(module_type="single_level_column_mux_array", columns=16, port=0, word_size=4) self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4) + a = factory.create(module_type="single_level_column_mux_array", columns=32, port=0, word_size=4) self.local_check(a) globals.end_openram() From 34209dac3d6aa9680e1311ed34cac7cbb44199a3 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 2 Jun 2020 16:50:07 -0700 Subject: [PATCH 29/42] A port option for correct mirroring in port_data. --- compiler/modules/hierarchical_predecode.py | 2 -- compiler/modules/port_data.py | 1 + compiler/modules/precharge_array.py | 3 +-- .../07_single_level_column_mux_array_pbitcell_test.py | 8 ++++---- compiler/tests/08_precharge_array_1rw1r_test.py | 2 +- compiler/tests/08_precharge_array_test.py | 2 +- compiler/tests/18_port_data_1rw_1r_test.py | 2 +- 7 files changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index fa44b080..d998d882 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -40,7 +40,6 @@ class hierarchical_predecode(design.design): def add_modules(self): """ Add the INV and AND gate modules """ - # FIXME: Default parms are required for hard cells for now. if self.number_of_inputs == 2: self.and_mod = factory.create(module_type="and2_dec", height=self.cell_height) @@ -60,7 +59,6 @@ class hierarchical_predecode(design.design): size=1) self.add_mod(self.inv) - def create_layout(self): """ The general organization is from left to right: 1) a set of M2 rails for input signals diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 904c82eb..ac6022f7 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -196,6 +196,7 @@ class port_data(design.design): if self.col_addr_size > 0: self.column_mux_array = factory.create(module_type="column_mux_array", columns=self.num_cols, + port=self.port, word_size=self.word_size, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port]) diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 3c64e85f..2acb1063 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -7,7 +7,6 @@ # import design import debug -from tech import drc from vector import vector from sram_factory import factory from globals import OPTS @@ -26,7 +25,7 @@ class precharge_array(design.design): self.columns = columns self.size = size - self.port = port + self.port = port self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br diff --git a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py index 84e62a96..7feb067e 100755 --- a/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py +++ b/compiler/tests/07_single_level_column_mux_array_pbitcell_test.py @@ -29,19 +29,19 @@ class single_level_column_mux_pbitcell_test(openram_test): factory.reset() debug.info(1, "Testing sample for 2-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=8, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="single_level_column_mux_array", columns=16, port=0, word_size=8, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 4-way column_mux_array in multi-port") - a = factory.create(module_type="single_level_column_mux_array", columns=16, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="single_level_column_mux_array", columns=16, port=0, word_size=4, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (innermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl0", bitcell_br="br0") + a = factory.create(module_type="single_level_column_mux_array", columns=32, port=0, word_size=4, bitcell_bl="bl0", bitcell_br="br0") self.local_check(a) debug.info(1, "Testing sample for 8-way column_mux_array in multi-port (outermost connections)") - a = factory.create(module_type="single_level_column_mux_array", columns=32, word_size=4, bitcell_bl="bl2", bitcell_br="br2") + a = factory.create(module_type="single_level_column_mux_array", columns=32, port=3, word_size=4, bitcell_bl="bl2", bitcell_br="br2") self.local_check(a) globals.end_openram() diff --git a/compiler/tests/08_precharge_array_1rw1r_test.py b/compiler/tests/08_precharge_array_1rw1r_test.py index 8f51c2d4..1381b93a 100755 --- a/compiler/tests/08_precharge_array_1rw1r_test.py +++ b/compiler/tests/08_precharge_array_1rw1r_test.py @@ -29,7 +29,7 @@ class precharge_test(openram_test): factory.reset() debug.info(2, "Checking 3 column precharge array for 1RW/1R bitcell") - pc = factory.create(module_type="precharge_array", columns=3, bitcell_bl="bl0", bitcell_br="br0") + pc = factory.create(module_type="precharge_array", columns=3, port=0, bitcell_bl="bl0", bitcell_br="br0") self.local_check(pc) # debug.info(2, "Checking 3 column precharge array for pbitcell (innermost connections)") diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py index b301a909..c3b823b3 100755 --- a/compiler/tests/08_precharge_array_test.py +++ b/compiler/tests/08_precharge_array_test.py @@ -23,7 +23,7 @@ class precharge_test(openram_test): # check precharge array in single port debug.info(2, "Checking 3 column precharge") - pc = factory.create(module_type="precharge_array", columns=3) + pc = factory.create(module_type="precharge_array", columns=3, port=0) self.local_check(pc) globals.end_openram() diff --git a/compiler/tests/18_port_data_1rw_1r_test.py b/compiler/tests/18_port_data_1rw_1r_test.py index e9e282a5..8081416e 100755 --- a/compiler/tests/18_port_data_1rw_1r_test.py +++ b/compiler/tests/18_port_data_1rw_1r_test.py @@ -36,7 +36,7 @@ class port_data_1rw_1r_test(openram_test): self.local_check(a) a = factory.create("port_data", sram_config=c, port=1) self.local_check(a) - + c.num_words=32 c.words_per_row=2 factory.reset() From 38f5e8b865e41577959f9eb243d63a466f6cc4f1 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 10:01:02 -0700 Subject: [PATCH 30/42] Add col mux tests for multiport --- .../04_single_level_column_mux_1rw_1r_test.py | 45 +++++++++++++++++++ .../tests/04_single_level_column_mux_test.py | 1 - ...ngle_level_column_mux_array_1rw_1r_test.py | 44 ++++++++++++++++++ .../07_single_level_column_mux_array_test.py | 2 - 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100755 compiler/tests/04_single_level_column_mux_1rw_1r_test.py create mode 100755 compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py diff --git a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py new file mode 100755 index 00000000..525191d7 --- /dev/null +++ b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class single_level_column_mux_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(2, "Checking column mux port 0") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + debug.info(2, "Checking column mux port 1") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py index 2b437987..20dfe968 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_single_level_column_mux_test.py @@ -15,7 +15,6 @@ from globals import OPTS from sram_factory import factory import debug -#@unittest.skip("SKIPPING 04_driver_test") class single_level_column_mux_test(openram_test): diff --git a/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py new file mode 100755 index 00000000..a8d804c9 --- /dev/null +++ b/compiler/tests/07_single_level_column_mux_array_1rw_1r_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_level_column_mux_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for 4-way column_mux_array port 0") + a = factory.create(module_type="single_level_column_mux_array", columns=8, port=0, word_size=2, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(a) + + debug.info(1, "Testing sample for 4-way column_mux_array port 1") + a = factory.create(module_type="single_level_column_mux_array", columns=8, port=0, word_size=2, bitcell_bl="bl1", bitcell_br="br1") + self.local_check(a) + + globals.end_openram() + + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py index fb608f68..ff7ec1c9 100755 --- a/compiler/tests/07_single_level_column_mux_array_test.py +++ b/compiler/tests/07_single_level_column_mux_array_test.py @@ -19,9 +19,7 @@ class single_level_column_mux_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - import single_level_column_mux_array - # check single level column mux array in single port debug.info(1, "Testing sample for 2-way column_mux_array") a = factory.create(module_type="single_level_column_mux_array", columns=16, port=0, word_size=8) self.local_check(a) From 7a602b75a47280399260235fb8ef0edd6e3d4f4b Mon Sep 17 00:00:00 2001 From: Joey Kunzler Date: Wed, 3 Jun 2020 12:54:15 -0700 Subject: [PATCH 31/42] keep dev routing changes to hierarchy_layout --- compiler/base/hierarchy_layout.py | 37 ++++--------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 36505294..c6f6ff49 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -701,7 +701,7 @@ class layout(): boundary = [self.find_lowest_coords(), self.find_highest_coords()] debug.check(boundary[0] and boundary[1], "No shapes to make a boundary.") - + height = boundary[1][1] - boundary[0][1] width = boundary[1][0] - boundary[0][0] (layer_number, layer_purpose) = techlayer[boundary_layer] @@ -1008,13 +1008,8 @@ class layout(): max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) - # max_x_lc & min_x_rc are for routing to/from the edge of the pins - # to increase spacing between contacts of different nets - max_x_lc = max([pin.lc().x for pin in pins]) - min_x_rc = min([pin.rc().x for pin in pins]) - # if we are less than a pitch, just create a non-preferred layer jog - if max_x_lc - min_x_rc <= pitch: + if max_x - min_x <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # Add the horizontal trunk on the vertical layer! @@ -1035,15 +1030,7 @@ class layout(): # Route each pin to the trunk for pin in pins: - # If there is sufficient space, Route from the edge of the pins - # Otherwise, route from the center of the pins - if max_x_lc - min_x_rc > pitch: - if pin.center().x == max_x: - mid = vector(pin.lc().x, trunk_offset.y) - else: - mid = vector(pin.rc().x, trunk_offset.y) - else: - mid = vector(pin.center().x, trunk_offset.y) + mid = vector(pin.center().x, trunk_offset.y) self.add_path(self.vertical_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) @@ -1060,13 +1047,8 @@ class layout(): max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) - # max_y_bc & min_y_uc are for routing to/from the edge of the pins - # to reduce spacing between contacts of different nets - max_y_bc = max([pin.bc().y for pin in pins]) - min_y_uc = min([pin.uc().y for pin in pins]) - # if we are less than a pitch, just create a non-preferred layer jog - if max_y_bc - min_y_uc <= pitch: + if max_y - min_y <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] @@ -1088,17 +1070,6 @@ class layout(): # Route each pin to the trunk for pin in pins: - # This code block currently causes drc violations for the topmost - # port when using multiport, TODO: fix or remove this block - # # If there is sufficient space, Route from the edge of the pins - # # Otherwise, route from the center of the pins - # if max_y_bc - min_y_uc > pitch: - # if pin.center().y == max_y: - # mid = vector(trunk_offset.x, pin.bc().y) - # else: - # mid = vector(trunk_offset.x, pin.uc().y) - # else: - # mid = vector(trunk_offset.x, pin.center().y) mid = vector(trunk_offset.x, pin.center().y) self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, From e93f3f1d2efed0b55ff7222846126e7533573e1c Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 14:30:15 -0700 Subject: [PATCH 32/42] Add 1rw_1r tests --- .../06_hierarchical_decoder_1rw_1r_test.py | 68 ++++++++++++++++++ .../tests/06_hierarchical_decoder_test.py | 6 -- ...6_hierarchical_predecode2x4_1rw_1r_test.py | 42 +++++++++++ .../06_hierarchical_predecode2x4_test.py | 6 -- ...6_hierarchical_predecode3x8_1rw_1r_test.py | 42 +++++++++++ .../06_hierarchical_predecode3x8_test.py | 6 -- .../08_wordline_driver_array_1rw_1r_test.py | 43 ++++++++++++ compiler/tests/18_port_address_1rw_1r_test.py | 40 +++++++++++ compiler/tests/18_port_address_test.py | 1 + .../tests/19_single_bank_wmask_1rw_1r_test.py | 69 +++++++++++++++++++ 10 files changed, 305 insertions(+), 18 deletions(-) create mode 100755 compiler/tests/06_hierarchical_decoder_1rw_1r_test.py create mode 100755 compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py create mode 100755 compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py create mode 100755 compiler/tests/08_wordline_driver_array_1rw_1r_test.py create mode 100755 compiler/tests/18_port_address_1rw_1r_test.py create mode 100755 compiler/tests/19_single_bank_wmask_1rw_1r_test.py diff --git a/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py new file mode 100755 index 00000000..5b317e6e --- /dev/null +++ b/compiler/tests/06_hierarchical_decoder_1rw_1r_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_decoder_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + # Checks 2x4 and 2-input NAND decoder + debug.info(1, "Testing 16 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) + self.local_check(a) + + # Checks 2x4 and 2-input NAND decoder with non-power-of-two + debug.info(1, "Testing 17 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) + self.local_check(a) + + # Checks 2x4 with 3x8 and 2-input NAND decoder + debug.info(1, "Testing 32 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) + self.local_check(a) + + # Checks 3 x 2x4 and 3-input NAND decoder + debug.info(1, "Testing 64 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=64) + self.local_check(a) + + # Checks 2x4 and 2 x 3x8 and 3-input NAND with non-power-of-two + debug.info(1, "Testing 132 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=132) + self.local_check(a) + + # Checks 3 x 3x8 and 3-input NAND decoder + debug.info(1, "Testing 512 row sample for hierarchical_decoder") + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index 7d155b01..e2b34cba 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -21,12 +21,6 @@ class hierarchical_decoder_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # Use the 2 port cell since it is usually bigger/easier - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 0 - # Checks 2x4 and 2-input NAND decoder debug.info(1, "Testing 16 row sample for hierarchical_decoder") a = factory.create(module_type="hierarchical_decoder", num_outputs=16) diff --git a/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py new file mode 100755 index 00000000..6512238f --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode2x4_1rw_1r_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_predecode2x4_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode2x4") + a = factory.create(module_type="hierarchical_predecode2x4") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index 94fd4838..c2b51f10 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -21,12 +21,6 @@ class hierarchical_predecode2x4_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # Use the 2 port cell since it is usually bigger/easier - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 0 - debug.info(1, "Testing sample for hierarchy_predecode2x4") a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) diff --git a/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py new file mode 100755 index 00000000..466a7e40 --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode3x8_1rw_1r_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class hierarchical_predecode3x8_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode3x8") + a = factory.create(module_type="hierarchical_predecode3x8") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index 33f94d9d..c1471a40 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -21,12 +21,6 @@ class hierarchical_predecode3x8_test(openram_test): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # Use the 2 port cell since it is usually bigger/easier - OPTS.bitcell = "bitcell_1rw_1r" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 0 - debug.info(1, "Testing sample for hierarchy_predecode3x8") a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) diff --git a/compiler/tests/08_wordline_driver_array_1rw_1r_test.py b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py new file mode 100755 index 00000000..7c97ff75 --- /dev/null +++ b/compiler/tests/08_wordline_driver_array_1rw_1r_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class wordline_driver_array_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + # check wordline driver for single port + debug.info(2, "Checking driver") + tx = factory.create(module_type="wordline_driver_array", rows=8, cols=32) + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_address_1rw_1r_test.py b/compiler/tests/18_port_address_1rw_1r_test.py new file mode 100755 index 00000000..402b30a0 --- /dev/null +++ b/compiler/tests/18_port_address_1rw_1r_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class port_address_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # Use the 2 port cell since it is usually bigger/easier + OPTS.bitcell = "bitcell_1rw_1r" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + + debug.info(1, "Port address 16 rows") + a = factory.create("port_address", cols=16, rows=16) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_address_test.py b/compiler/tests/18_port_address_test.py index 23f35540..a2a8bf0b 100755 --- a/compiler/tests/18_port_address_test.py +++ b/compiler/tests/18_port_address_test.py @@ -13,6 +13,7 @@ from globals import OPTS from sram_factory import factory import debug + class port_address_test(openram_test): def runTest(self): diff --git a/compiler/tests/19_single_bank_wmask_1rw_1r_test.py b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py new file mode 100755 index 00000000..d08ff8cc --- /dev/null +++ b/compiler/tests/19_single_bank_wmask_1rw_1r_test.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class single_bank_wmask_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + + c = sram_config(word_size=8, + write_size=4, + num_words=16, + num_banks=1) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("bank", sram_config=c) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) From 3b1fe26d252ded614cd61a9efa4ec4e50c8fde62 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 14:33:30 -0700 Subject: [PATCH 33/42] Spacing between decoder and driver for s8 --- compiler/modules/hierarchical_decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 8b711031..2b624ddc 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -191,7 +191,7 @@ class hierarchical_decoder(design.design): + self.predecoder_width \ + self.internal_routing_width \ + self.nand_width \ - + self.m1_space + + 2 * self.m1_pitch def route_inputs(self): """ Create input bus for the predecoders """ From 4bc1e9a026cb412374b90945ac6a22642635ae1e Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 15:47:03 -0700 Subject: [PATCH 34/42] Fix the bitline spacing in the column mux to a constant. --- compiler/pgates/single_level_column_mux.py | 101 ++++++------------ .../04_single_level_column_mux_1rw_1r_test.py | 2 +- 2 files changed, 32 insertions(+), 71 deletions(-) diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index bdd03e55..96935158 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -13,6 +13,7 @@ from sram_factory import factory import logical_effort from utils import round_to_grid + class single_level_column_mux(pgate.pgate): """ This module implements the columnmux bitline cell used in the design. @@ -44,9 +45,18 @@ class single_level_column_mux(pgate.pgate): def create_layout(self): - self.pin_height = 2 * self.m2_width + # If li exists, use li and m1 for the mux, otherwise use m1 and m2 + if "li" in layer: + self.col_mux_stack = self.li_stack + else: + self.col_mux_stack = self.m1_stack + self.pin_layer = self.bitcell.get_pin(self.bitcell_bl).layer + self.pin_pitch = getattr(self, "{}_pitch".format(self.pin_layer)) + self.pin_width = getattr(self, "{}_width".format(self.pin_layer)) + self.pin_height = 2 * self.pin_width self.width = self.bitcell.width self.height = self.nmos_upper.uy() + self.pin_height + self.connect_poly() self.add_bitline_pins() self.connect_bitlines() @@ -58,9 +68,7 @@ class single_level_column_mux(pgate.pgate): # Adds nmos_lower,nmos_upper to the module self.ptx_width = self.tx_size * drc("minwidth_tx") self.nmos = factory.create(module_type="ptx", - width=self.ptx_width, - add_source_contact=False, - add_drain_contact=False) + width=self.ptx_width) self.add_mod(self.nmos) def add_pins(self): @@ -69,40 +77,26 @@ class single_level_column_mux(pgate.pgate): def add_bitline_pins(self): """ Add the top and bottom pins to this cell """ - bl_pin=self.bitcell.get_pin(self.bitcell_bl) - br_pin=self.bitcell.get_pin(self.bitcell_br) - - bl_pos = vector(bl_pin.lx(), 0) - br_pos = vector(br_pin.lx(), 0) - - # The bitline input/output pins must be a least as wide as the metal pitch - # so that there is enough space to route to/from the pins. - # FIXME: bitline_metal_pitch should be greater than the horizontal metal pitch used in port_data - bitline_metal_pitch = self.width / 2 - bitline_width = br_pos.x - bl_pos.x - if bitline_width < bitline_metal_pitch: - bitline_width_increase_bl = round_to_grid((bitline_metal_pitch - bitline_width) / 2) - bitline_width_increase_br = round_to_grid((bitline_metal_pitch - bitline_width) - bitline_width_increase_bl) - bl_pos = bl_pos + vector(-bitline_width_increase_bl, 0) - br_pos = br_pos + vector( bitline_width_increase_br, 0) + bl_pos = vector(self.pin_pitch, 0) + br_pos = vector(self.width - self.pin_pitch, 0) # bl and br self.add_layout_pin(text="bl", - layer=bl_pin.layer, + layer=self.pin_layer, offset=bl_pos + vector(0, self.height - self.pin_height), height=self.pin_height) self.add_layout_pin(text="br", - layer=br_pin.layer, + layer=self.pin_layer, offset=br_pos + vector(0, self.height - self.pin_height), height=self.pin_height) # bl_out and br_out self.add_layout_pin(text="bl_out", - layer=bl_pin.layer, + layer=self.pin_layer, offset=bl_pos, height=self.pin_height) self.add_layout_pin(text="br_out", - layer=br_pin.layer, + layer=self.pin_layer, offset=br_pos, height=self.pin_height) @@ -110,7 +104,7 @@ class single_level_column_mux(pgate.pgate): """ Create the two pass gate NMOS transistors to switch the bitlines""" # Space it in the center - nmos_lower_position = self.nmos.active_offset.scale(0,1) \ + nmos_lower_position = self.nmos.active_offset.scale(0, 1) \ + vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0) self.nmos_lower = self.add_inst(name="mux_tx1", mod=self.nmos, @@ -145,62 +139,29 @@ class single_level_column_mux(pgate.pgate): def connect_bitlines(self): """ Connect the bitlines to the mux transistors """ - # If li exists, use li and m1 for the mux, otherwise use m1 and m2 - if "li" in layer: - self.col_mux_stack = self.li_stack - else: - self.col_mux_stack = self.m1_stack - - # These are on metal2 bl_pin = self.get_pin("bl") br_pin = self.get_pin("br") bl_out_pin = self.get_pin("bl_out") br_out_pin = self.get_pin("br_out") - # These are on metal1 nmos_lower_s_pin = self.nmos_lower.get_pin("S") nmos_lower_d_pin = self.nmos_lower.get_pin("D") nmos_upper_s_pin = self.nmos_upper.get_pin("S") nmos_upper_d_pin = self.nmos_upper.get_pin("D") # Add vias to bl, br_out, nmos_upper/S, nmos_lower/D - self.add_via_center(layers=self.col_mux_stack, - offset=bl_pin.bc(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=br_out_pin.uc(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=nmos_upper_s_pin.center(), - directions=("V", "V")) - self.add_via_center(layers=self.col_mux_stack, - offset=nmos_lower_d_pin.center(), - directions=("V", "V")) - - # Add diffusion contacts - # These were previously omitted with the options: add_source_contact=False, add_drain_contact=False - # They are added now and not previously so that they do not include m1 (which is usually included by default) - # This is only a concern when the local interconnect (li) layer is being used - self.add_via_center(layers=self.active_stack, - offset=nmos_upper_d_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_lower_s_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_upper_s_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") - self.add_via_center(layers=self.active_stack, - offset=nmos_lower_d_pin.center(), - directions=("V", "V"), - implant_type="n", - well_type="nwell") + self.add_via_stack_center(from_layer=bl_pin.layer, + to_layer=self.col_mux_stack[0], + offset=bl_pin.bc()) + self.add_via_stack_center(from_layer=br_out_pin.layer, + to_layer=self.col_mux_stack[0], + offset=br_out_pin.uc()) + self.add_via_stack_center(from_layer=nmos_upper_s_pin.layer, + to_layer=self.col_mux_stack[2], + offset=nmos_upper_s_pin.center()) + self.add_via_stack_center(from_layer=nmos_lower_d_pin.layer, + to_layer=self.col_mux_stack[2], + offset=nmos_lower_d_pin.center()) # bl -> nmos_upper/D on metal1 # bl_out -> nmos_upper/S on metal2 diff --git a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py index 525191d7..69b31cc6 100755 --- a/compiler/tests/04_single_level_column_mux_1rw_1r_test.py +++ b/compiler/tests/04_single_level_column_mux_1rw_1r_test.py @@ -32,7 +32,7 @@ class single_level_column_mux_1rw_1r_test(openram_test): self.local_check(tx) debug.info(2, "Checking column mux port 1") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl1", bitcell_br="br1") self.local_check(tx) globals.end_openram() From 4183638f0354cb7ac909c1d6752b67a137d1191d Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 16:05:57 -0700 Subject: [PATCH 35/42] Align precharge bitlines with col mux --- compiler/pgates/precharge.py | 12 ++++++------ ...w1r_test.py => 08_precharge_array_1rw_1r_test.py} | 0 2 files changed, 6 insertions(+), 6 deletions(-) rename compiler/tests/{08_precharge_array_1rw1r_test.py => 08_precharge_array_1rw_1r_test.py} (100%) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 8b84a1b8..278fef3d 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -235,11 +235,11 @@ class precharge(design.design): """ Adds both bit-line and bit-line-bar to the module """ - layer_width = drc("minwidth_" + self.bitline_layer) - layer_space = drc("{0}_to_{0}".format(self.bitline_layer)) + layer_pitch = getattr(self, "{}_pitch".format(self.bitline_layer)) + layer_width = getattr(self, "{}_width".format(self.bitline_layer)) - # adds the BL - self.bl_xoffset = layer_space + 0.5 * layer_width + # adds the BL so it aligns with the col mux + self.bl_xoffset = layer_pitch + 0.5 * layer_width top_pos = vector(self.bl_xoffset, self.height) pin_pos = vector(self.bl_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) @@ -248,8 +248,8 @@ class precharge(design.design): start=pin_pos, end=top_pos) - # adds the BR - self.br_xoffset = self.width - layer_space - 0.5 * layer_width + # adds the BR so it aligns with the col mux + self.br_xoffset = self.width - layer_pitch - 0.5 * layer_width top_pos = vector(self.br_xoffset, self.height) pin_pos = vector(self.br_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) diff --git a/compiler/tests/08_precharge_array_1rw1r_test.py b/compiler/tests/08_precharge_array_1rw_1r_test.py similarity index 100% rename from compiler/tests/08_precharge_array_1rw1r_test.py rename to compiler/tests/08_precharge_array_1rw_1r_test.py From b2b7e7800bd01ac27e4051a6f78764d549086205 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 16:39:05 -0700 Subject: [PATCH 36/42] Undo same bitline pitch --- compiler/pgates/precharge.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 278fef3d..893736f4 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -236,10 +236,9 @@ class precharge(design.design): Adds both bit-line and bit-line-bar to the module """ layer_pitch = getattr(self, "{}_pitch".format(self.bitline_layer)) - layer_width = getattr(self, "{}_width".format(self.bitline_layer)) - # adds the BL so it aligns with the col mux - self.bl_xoffset = layer_pitch + 0.5 * layer_width + # adds the BL + self.bl_xoffset = layer_pitch top_pos = vector(self.bl_xoffset, self.height) pin_pos = vector(self.bl_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) @@ -248,8 +247,8 @@ class precharge(design.design): start=pin_pos, end=top_pos) - # adds the BR so it aligns with the col mux - self.br_xoffset = self.width - layer_pitch - 0.5 * layer_width + # adds the BR + self.br_xoffset = self.width - layer_pitch top_pos = vector(self.br_xoffset, self.height) pin_pos = vector(self.br_xoffset, 0) self.add_path(self.bitline_layer, [top_pos, pin_pos]) From 3927c62e3269f9fa4a0860fdf0a259bc3a14477b Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 16:39:33 -0700 Subject: [PATCH 37/42] Undo extra space due to nwell spacing --- compiler/modules/hierarchical_decoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 2b624ddc..8b711031 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -191,7 +191,7 @@ class hierarchical_decoder(design.design): + self.predecoder_width \ + self.internal_routing_width \ + self.nand_width \ - + 2 * self.m1_pitch + + self.m1_space def route_inputs(self): """ Create input bus for the predecoders """ From 77c95b28da19b37ae638a50b85d53c7bb06bb206 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 16:39:46 -0700 Subject: [PATCH 38/42] Rename precharge test --- compiler/tests/08_precharge_array_1rw_1r_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/tests/08_precharge_array_1rw_1r_test.py b/compiler/tests/08_precharge_array_1rw_1r_test.py index 1381b93a..dde36b23 100755 --- a/compiler/tests/08_precharge_array_1rw_1r_test.py +++ b/compiler/tests/08_precharge_array_1rw_1r_test.py @@ -15,7 +15,7 @@ from globals import OPTS from sram_factory import factory import debug -class precharge_test(openram_test): +class precharge_1rw_1r_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) From 249b5355ba8ed083e011c000c3f3fb3ed6f8b01f Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Jun 2020 17:08:04 -0700 Subject: [PATCH 39/42] Adjust rbl route --- compiler/modules/bank.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 393631e5..2aa4e871 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -128,10 +128,10 @@ 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, 1.5 * self.m2_pitch) + via_offset = bl_pin.uc() + vector(0, self.m2_pitch) left_right_offset = vector(self.max_x_offset, via_offset.y) else: - via_offset = bl_pin.bc() - vector(0, 1.5 * self.m2_pitch) + via_offset = bl_pin.bc() - vector(0, self.m2_pitch) left_right_offset = vector(self.min_x_offset, via_offset.y) self.add_via_stack_center(from_layer=bl_pin.layer, to_layer="m3", From 7aafa438978cd4f38afa501d40357ddea0593ce6 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 4 Jun 2020 10:22:52 -0700 Subject: [PATCH 40/42] Connect RBL to bottom of precharge cell --- compiler/modules/bank.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 2aa4e871..92e4e8cd 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -123,23 +123,22 @@ class bank(design.design): def route_rbl(self, port): """ Route the rbl_bl and rbl_wl """ - - bl_pin_name = self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) - bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name) - # This will ensure the pin is only on the top or bottom edge + + # Connect the rbl to the port data pin + bl_pin = self.port_data_inst[port].get_pin("rbl_bl") if port % 2: - via_offset = bl_pin.uc() + vector(0, self.m2_pitch) - left_right_offset = vector(self.max_x_offset, via_offset.y) + pin_offset = bl_pin.uc() + left_right_offset = vector(self.max_x_offset, pin_offset.y) else: - via_offset = bl_pin.bc() - vector(0, self.m2_pitch) - left_right_offset = vector(self.min_x_offset, via_offset.y) + pin_offset = bl_pin.bc() + left_right_offset = vector(self.min_x_offset, pin_offset.y) self.add_via_stack_center(from_layer=bl_pin.layer, to_layer="m3", - offset=via_offset) + offset=pin_offset) self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port), layer="m3", start=left_right_offset, - end=via_offset) + end=pin_offset) def route_bitlines(self, port): """ Route the bitlines depending on the port type rw, w, or r. """ From 717188f85c47dc8dccaa822ef5d7b20e5cdec2b5 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 4 Jun 2020 11:03:39 -0700 Subject: [PATCH 41/42] Change L shape of rbl route --- compiler/sram/sram_1bank.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 8dbcfaf4..f9a40545 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -320,7 +320,9 @@ class sram_1bank(sram_base): # 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(src_pin, dest_pin) + self.add_wire(self.m2_stack[::-1], + [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) + # self.connect_hbus(src_pin, dest_pin) def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ From e06dc3810a333819ba1fdb21258c77cfcbf94f41 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 4 Jun 2020 12:12:19 -0700 Subject: [PATCH 42/42] Move precharge pin to bottom --- compiler/pgates/precharge.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 893736f4..cfc2a688 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -191,14 +191,15 @@ class precharge(design.design): """ # adds the en contact to connect the gates to the en rail - # midway in the 4 M2 tracks - offset = self.lower_pmos_inst.get_pin("G").ul() \ - + vector(0, 0.5 * self.m2_pitch) + pin_offset = self.lower_pmos_inst.get_pin("G").lr() + # This is an extra space down for some techs with contact to active spacing + offset = pin_offset - vector(0, self.poly_space) self.add_via_stack_center(from_layer="poly", to_layer=self.en_layer, offset=offset) - - # adds the en rail on metal1 + self.add_path("poly", + [self.lower_pmos_inst.get_pin("G").bc(), offset]) + # adds the en rail self.add_layout_pin_segment_center(text="en_bar", layer=self.en_layer, start=offset.scale(0, 1),