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())