From 71a1dd8f38471016ef59702fea2ad90bad199ace Mon Sep 17 00:00:00 2001 From: jcirimel Date: Tue, 5 May 2020 16:35:51 -0700 Subject: [PATCH 1/4] fix tx binning in col mux for memories with >1 word per row --- compiler/pgates/pgate.py | 5 +++-- compiler/pgates/precharge.py | 2 +- compiler/pgates/ptx.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index a942b35f..e845f018 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -286,9 +286,10 @@ class pgate(design.design): self.width = max(self.nwell_contact.rx(), self.pwell_contact.rx()) + self.m1_space + 0.5 * contact.m1_via.width self.well_width = self.width + 2 * self.nwell_enclose_active # Height is an input parameter, so it is not recomputed. - - def bin_width(self, tx_type, target_width): + @staticmethod + def bin_width(tx_type, target_width): + if tx_type == "nmos": bins = nmos_bins[drc("minwidth_poly")] elif tx_type == "pmos": diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 45128421..73edaa99 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -80,7 +80,7 @@ class precharge(design.design): Initializes the upper and lower pmos """ if(OPTS.tech_name == "s8"): - (self.ptx_width, self.ptx_mults) = pgate.bin_width(self, "pmos", self.ptx_width) + (self.ptx_width, self.ptx_mults) = pgate.bin_width("pmos", self.ptx_width) self.pmos = factory.create(module_type="ptx", width=self.ptx_width, mults=self.ptx_mults, diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index b4b20381..0cc3f951 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -14,7 +14,7 @@ import contact import logical_effort import os from globals import OPTS - +from pgate import pgate class ptx(design.design): """ @@ -109,6 +109,7 @@ class ptx(design.design): perimeter_sd = 2 * self.poly_width + 2 * self.tx_width if OPTS.tech_name == "s8": # s8 technology is in microns + (self.width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], self.mults, self.tx_width, From d8a51ecafb3c30b3407ae7408bd4f5b325d32b96 Mon Sep 17 00:00:00 2001 From: jcirimel Date: Tue, 5 May 2020 21:59:28 -0700 Subject: [PATCH 2/4] remove prints, scaling bug fix --- compiler/pgates/pgate.py | 8 +++++--- compiler/pgates/ptx.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index e845f018..918cd17b 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -304,22 +304,24 @@ class pgate(design.design): scaled_bin = bins[0] * scaling_factor else: + base_bins = [] scaled_bins = [] scaling_factors = [] scaled_bins.append(bins[-1]) + base_bins.append(bins[-1]) scaling_factors.append(1) for width in bins[0:-1]: m = math.ceil(target_width / width) + base_bins.append(width) scaling_factors.append(m) scaled_bins.append(m * width) select = bisect_left(scaled_bins, target_width) scaling_factor = scaling_factors[select] scaled_bin = scaled_bins[select] - select = (select + 1) % len(scaled_bins) - selected_bin = bins[select] + selected_bin = base_bins[select] - debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, scaled_bin, target_width)) + debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, selected_bin * scaling_factor, target_width)) return(selected_bin, scaling_factor) diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 0cc3f951..a5d9c3b5 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -109,7 +109,7 @@ class ptx(design.design): perimeter_sd = 2 * self.poly_width + 2 * self.tx_width if OPTS.tech_name == "s8": # s8 technology is in microns - (self.width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) + (self.tx_width, self.mults) = pgate.bin_width(self.tx_type, self.tx_width) main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], self.mults, self.tx_width, From dd73afc983951603788246d0bb578942f68409c2 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 23 Apr 2020 14:43:54 -0700 Subject: [PATCH 3/4] 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 4/4] 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()