diff --git a/compiler/base/design.py b/compiler/base/design.py index c2ef8340..73ace38e 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -74,7 +74,7 @@ class design(hierarchy_design): return pitch def setup_drc_constants(self): - """ + """ These are some DRC constants used in many places in the compiler. """ diff --git a/compiler/base/errors.py b/compiler/base/errors.py new file mode 100644 index 00000000..e2b9e5ec --- /dev/null +++ b/compiler/base/errors.py @@ -0,0 +1,15 @@ + + +class drc_error(Exception): + """Exception raised for DRC errors. + + Attributes: + expression -- input expression in which the error occurred + message -- explanation of the error + """ + + # def __init__(self, expression, message): + # self.expression = expression + # self.message = message + def __init__(self, message): + self.message = message diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 63d53d39..b970666d 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -46,7 +46,6 @@ class layout(): except ImportError: self.pwr_grid_layer = "m3" - ############################################################ # GDS layout ############################################################ @@ -196,7 +195,7 @@ class layout(): self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons - #debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) + # debug.info(4, "instance list: " + ",".join(x.name for x in self.insts)) return self.insts[-1] def get_inst(self, name): @@ -214,12 +213,14 @@ class layout(): width = drc["minwidth_{}".format(layer)] if not height: height = drc["minwidth_{}".format(layer)] - # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] - if lpp[0] >= 0: - self.objs.append(geometry.rectangle(lpp, offset, width, height)) - return self.objs[-1] - return None + if abs(offset[0]-5.16250)<0.01 and abs(offset[1]-8.70750)<0.01: + import pdb; pdb.set_trace() + self.objs.append(geometry.rectangle(lpp, + offset, + width, + height)) + return self.objs[-1] def add_rect_center(self, layer, offset, width=None, height=None): """ @@ -230,16 +231,13 @@ class layout(): width = drc["minwidth_{}".format(layer)] if not height: height = drc["minwidth_{}".format(layer)] - # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] corrected_offset = offset - vector(0.5 * width, 0.5 * height) - if lpp[0] >= 0: - self.objs.append(geometry.rectangle(lpp, - corrected_offset, - width, - height)) - return self.objs[-1] - return None + self.objs.append(geometry.rectangle(lpp, + corrected_offset, + width, + height)) + return self.objs[-1] def add_segment_center(self, layer, start, end): """ @@ -252,15 +250,15 @@ class layout(): elif start.x != end.x: offset = vector(0, 0.5 * minwidth_layer) return self.add_rect(layer, - start-offset, - end.x-start.x, + start - offset, + end.x - start.x, minwidth_layer) else: offset = vector(0.5 * minwidth_layer, 0) return self.add_rect(layer, - start-offset, + start - offset, minwidth_layer, - end.y-start.y) + end.y - start.y) def get_pin(self, text): """ @@ -268,14 +266,14 @@ class layout(): """ try: if len(self.pin_map[text]) > 1: - debug.error("Should use a pin iterator since more than one pin {}".format(text),-1) + debug.error("Should use a pin iterator since more than one pin {}".format(text), -1) # If we have one pin, return it and not the list. # Otherwise, should use get_pins() any_pin = next(iter(self.pin_map[text])) return any_pin except Exception: self.gds_write("missing_pin.gds") - debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1) + debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text, self.name), -1) def get_pins(self, text): """ @@ -377,7 +375,7 @@ class layout(): height = drc["minwidth_{0}".format(layer)] new_pin = pin_layout(text, - [offset, offset+vector(width, height)], + [offset, offset + vector(width, height)], layer) try: @@ -413,23 +411,18 @@ class layout(): def add_label(self, text, layer, offset=[0, 0], zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" - # negative layers indicate "unused" layers in a given technology debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset)) lpp = techlayer[layer] - if lpp[0] >= 0: - self.objs.append(geometry.label(text, lpp, offset, zoom)) - return self.objs[-1] - return None + self.objs.append(geometry.label(text, lpp, offset, zoom)) + return self.objs[-1] def add_path(self, layer, coordinates, width=None): """Connects a routing path on given layer,coordinates,width.""" debug.info(4, "add path " + str(layer) + " " + str(coordinates)) import wire_path # NOTE: (UNTESTED) add_path(...) is currently not used - # negative layers indicate "unused" layers in a given technology # lpp = techlayer[layer] - # if lpp[0] >= 0: - # self.objs.append(geometry.path(lpp, coordinates, width)) + # self.objs.append(geometry.path(lpp, coordinates, width)) wire_path.wire_path(obj=self, layer=layer, @@ -465,7 +458,7 @@ class layout(): from tech import preferred_directions return preferred_directions[layer] - def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None): + 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: @@ -487,7 +480,7 @@ class layout(): self.connect_inst([]) return inst - def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None): + def add_via_center(self, layers, offset, directions=None, size=[1, 1], implant_type=None, well_type=None): """ Add a three layer via structure by the center coordinate accounting for mirroring and rotation. @@ -799,9 +792,7 @@ class layout(): self.add_rect(layer=layer, offset=line_offset, height=length) - # Make this the center of the rail - line_positions[names[i]] = line_offset + vector(half_minwidth, - 0.5 * length) + line_positions[names[i]] = line_offset + vector(half_minwidth, 0) else: for i in range(len(names)): line_offset = offset + vector(0, @@ -953,14 +944,14 @@ class layout(): min_y = min([pin.center().y for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog - if max_y-min_y <= pitch: + if max_y - min_y <= pitch: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] # Add the vertical trunk on the horizontal layer! self.add_path(self.horizontal_layer, [vector(trunk_offset.x, min_y - half_layer_width), - vector(trunk_offset.x,max_y + half_layer_width)]) + vector(trunk_offset.x, max_y + half_layer_width)]) # Route each pin to the trunk for pin in pins: @@ -983,6 +974,7 @@ class layout(): def create_channel_route(self, netlist, offset, layer_stack, + layer_dirs=None, vertical=False): """ The net list is a list of the nets. Each net is a list of pins @@ -1000,7 +992,7 @@ class layout(): # Remove the pin from all conflicts # FIXME: This is O(n^2), so maybe optimize it. - for other_pin,conflicts in g.items(): + for other_pin, conflicts in g.items(): if pin in conflicts: conflicts.remove(pin) g[other_pin]=conflicts @@ -1021,25 +1013,38 @@ class layout(): def vcg_pin_overlap(pin1, pin2, vertical, pitch): """ Check for vertical or horizontal overlap of the two pins """ + # FIXME: If the pins are not in a row, this may break. # However, a top pin shouldn't overlap another top pin, # for example, so the # extra comparison *shouldn't* matter. # Pin 1 must be in the "BOTTOM" set - x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x) self.horiz_layer_contact_width: + width = self.horiz_layer_contact_width + else: + width = self.horiz_layer_width temp_offset = [pl[index][0], - pl[index][1] - 0.5*self.horiz_layer_width] + pl[index][1] - 0.5 * width] + # If we go in the negative direction, move the offset if line_length < 0: temp_offset = [temp_offset[0] + line_length, temp_offset[1]] @@ -91,10 +128,17 @@ class wire(wire_path): length=abs(line_length), offset=temp_offset, orientation="horizontal", - layer_width=self.horiz_layer_width) + layer_width=width) + # Vertical wire segment elif pl[index][1] != pl[index + 1][1]: line_length = pl[index + 1][1] - pl[index][1] - temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width, + # 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: + width = self.vert_layer_contact_width + else: + width = self.vert_layer_width + temp_offset = [pl[index][0] - 0.5 * width, pl[index][1]] if line_length < 0: temp_offset = [temp_offset[0], @@ -103,11 +147,13 @@ class wire(wire_path): length=abs(line_length), offset=temp_offset, orientation="vertical", - layer_width=self.vert_layer_width) + layer_width=width) def assert_node(self, A, B): - """ Check if the node movements are not big enough for the - technology sizes.""" + """ + Check if the node movements are not big enough for the + technology sizes. + """ X_diff = abs(A[0] - B[0]) Y_diff = abs(A[1] - B[1]) [minX, minY] = self.node_to_node diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 58a9e3ed..b5a143cf 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -34,6 +34,10 @@ class stimuli(): self.sf = stim_file (self.process, self.voltage, self.temperature) = corner + try: + self.device_libraries = tech.spice["fet_libraries"][self.process] + except: + debug.info(2, "Not using spice library") self.device_models = tech.spice["fet_models"][self.process] self.sram_name = "Xsram" @@ -247,8 +251,17 @@ class stimuli(): def write_include(self, circuit): """Writes include statements, inputs are lists of model files""" + includes = self.device_models + [circuit] self.sf.write("* {} process corner\n".format(self.process)) + if OPTS.tech_name == "s8": + libraries = self.device_libraries + for item in list(libraries): + if os.path.isfile(item[0]): + self.sf.write(".lib \"{0}\" {1}\n".format(item[0], item[1])) + else: + debug.error("Could not find spice library: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item[0])) + for item in list(includes): if os.path.isfile(item): self.sf.write(".include \"{0}\"\n".format(item)) diff --git a/compiler/drc/design_rules.py b/compiler/drc/design_rules.py index 05168b77..b194d082 100644 --- a/compiler/drc/design_rules.py +++ b/compiler/drc/design_rules.py @@ -9,9 +9,10 @@ import debug from drc_value import * from drc_lut import * + class design_rules(dict): - """ - This is a class that implements the design rules structures. + """ + This is a class that implements the design rules structures. """ def __init__(self, name): self.tech_name = name diff --git a/compiler/drc/drc_lut.py b/compiler/drc/drc_lut.py index 0ad0fde4..8a7b49d2 100644 --- a/compiler/drc/drc_lut.py +++ b/compiler/drc/drc_lut.py @@ -7,9 +7,10 @@ # import debug + class drc_lut(): - """ - Implement a lookup table of rules. + """ + Implement a lookup table of rules. Each element is a tuple with the last value being the rule. It searches through backwards until all of the key values are met and returns the rule value. @@ -31,7 +32,6 @@ class drc_lut(): for table_key in sorted(self.table.keys(), reverse=True): if self.match(key, table_key): return self.table[table_key] - def match(self, key1, key2): """ @@ -39,8 +39,8 @@ class drc_lut(): (i.e. return false if key1= self.sen_delay_rise or - self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall): + if (self.wl_delay_rise * self.wl_timing_tolerance >= self.sen_delay_rise or + self.wl_delay_fall * self.wl_timing_tolerance >= self.sen_delay_fall): return False else: return True @@ -240,91 +237,107 @@ class control_logic(design.design): # The sen delay must always be bigger than than the wl # delay. This decides how much larger the sen delay must be # before a re-size is warranted. - if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay: + if self.wl_delay * self.wl_timing_tolerance >= self.sen_delay: return False else: - return True + return True def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout): """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" from math import ceil - previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages + previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) - - delay_fanout = 3 # This can be anything >=2 + + # This can be anything >=2 + delay_fanout = 3 # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay) + required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay) debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") - delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay)) - if delay_stages%2 == 1: #force an even number of stages. - delay_stages+=1 + delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) + # force an even number of stages. + if delay_stages % 2 == 1: + delay_stages += 1 # Fanout can be varied as well but is a little more complicated but potentially optimal. debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) return (delay_stages, delay_fanout) def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout): """Determine the size of the delay chain used for the Sense Amp Enable using path delays""" - - previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages + + previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay + previous_delay_chain_delay = previous_delay_per_stage * previous_stages debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) fanout_rise = fanout_fall = 2 # This can be anything >=2 # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value - required_delay_fall = self.wl_delay_fall*self.wl_timing_tolerance - (self.sen_delay_fall-previous_delay_chain_delay/2) - required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2) - debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise)) + required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \ + (self.sen_delay_fall - previous_delay_chain_delay / 2) + required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \ + (self.sen_delay_rise - previous_delay_chain_delay / 2) + debug.info(2, + "Required delays from chain: fall={}, rise={}".format(required_delay_fall, + required_delay_rise)) # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. WARNING_FANOUT_DIFF = 5 stages_close = False # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) while True: - stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall) - stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise) - debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise)) - if abs(stages_fall-stages_rise) == 1 and not stages_close: + stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall, + fanout_fall) + stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise, + fanout_rise) + debug.info(1, + "Fall stages={}, rise stages={}".format(stages_fall, + stages_rise)) + if abs(stages_fall - stages_rise) == 1 and not stages_close: stages_close = True safe_fanout_rise = fanout_rise safe_fanout_fall = fanout_fall - if stages_fall == stages_rise: + if stages_fall == stages_rise: break - elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise): + elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise): debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.") fanout_rise = safe_fanout_rise fanout_fall = safe_fanout_fall break - # There should also be a condition to make sure the fanout does not get too large. + # There should also be a condition to make sure the fanout does not get too large. # Otherwise, increase the fanout of delay with the most stages, calculate new stages elif stages_fall>stages_rise: fanout_fall+=1 else: fanout_rise+=1 - total_stages = max(stages_fall,stages_rise)*2 + total_stages = max(stages_fall, stages_rise) * 2 debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. - stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)] + stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)] return stage_list def calculate_stages_with_fixed_fanout(self, required_delay, fanout): from math import ceil # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay - if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0). + # 3 is the minimum delay per stage (with pinv=0). + if required_delay <= 3 + self.inv_parasitic_delay: return 1 - delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay)) + delay_per_stage = fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) return delay_stages def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall): - """Produces a list of fanouts which determine the size of the delay chain. List length is the number of stages. - Assumes the first stage is falling. + """ + Produces a list of fanouts which determine the size of the delay chain. + List length is the number of stages. + Assumes the first stage is falling. """ stage_list = [] for i in range(total_stages): - if i%2 == 0: + if i % 2 == 0: stage_list.append() def setup_signal_busses(self): @@ -351,7 +364,7 @@ class control_logic(design.design): else: self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"] # leave space for the bus plus one extra space - self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch + self.internal_bus_width = (len(self.internal_bus_list) + 1) * self.m2_pitch # Outputs to the bank if self.port_type == "rw": @@ -366,15 +379,13 @@ class control_logic(design.design): self.supply_list = ["vdd", "gnd"] - def route_rails(self): """ Add the input signal inverted tracks """ height = self.control_logic_center.y - self.m2_pitch - offset = vector(self.ctrl_dff_array.width,0) + 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) - - + def create_instances(self): """ Create all the instances """ self.create_dffs() @@ -388,9 +399,7 @@ class control_logic(design.design): if (self.port_type == "rw") or (self.port_type == "r"): self.create_sen_row() self.create_delay() - self.create_pen_row() - - + self.create_pen_row() def place_instances(self): """ Place all the instances """ @@ -406,13 +415,13 @@ class control_logic(design.design): row = 0 # Add the logic on the right of the bus - self.place_clk_buf_row(row) + self.place_clk_buf_row(row) row += 1 - self.place_gated_clk_bar_row(row) + self.place_gated_clk_bar_row(row) row += 1 - self.place_gated_clk_buf_row(row) + self.place_gated_clk_buf_row(row) row += 1 - self.place_wlen_row(row) + self.place_wlen_row(row) row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_wen_row(row) @@ -421,10 +430,10 @@ class control_logic(design.design): row += 1 self.place_pen_row(row) row += 1 - if (self.port_type == "rw") or (self.port_type == "w"): + if (self.port_type == "rw") or (self.port_type == "w"): self.place_rbl_delay_row(row) row += 1 - if (self.port_type == "rw") or (self.port_type == "r"): + if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 self.place_delay(row) @@ -435,11 +444,11 @@ class control_logic(design.design): self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) # Extra pitch on top and right - self.height = height + 2*self.m1_pitch + self.height = height + 2 * self.m1_pitch # Max of modules or logic rows self.width = max([inst.rx() for inst in self.row_end_inst]) if (self.port_type == "rw") or (self.port_type == "r"): - self.width = max(self.delay_inst.rx() , self.width) + self.width = max(self.delay_inst.rx(), self.width) self.width += self.m2_pitch def route_all(self): @@ -459,7 +468,6 @@ class control_logic(design.design): self.route_gated_clk_buf() self.route_supply() - def create_delay(self): """ Create the replica bitline """ self.delay_inst=self.add_inst(name="delay_chain", @@ -467,9 +475,9 @@ class control_logic(design.design): # rbl_bl_delay is asserted (1) when the bitline has been discharged self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"]) - def place_delay(self,row): + def place_delay(self, row): """ Place the replica bitline """ - y_off = row * self.and2.height + 2*self.m1_pitch + y_off = row * self.and2.height + 2 * self.m1_pitch # Add the RBL above the rows # Add to the right of the control rows and routing channel @@ -482,24 +490,22 @@ class control_logic(design.design): # Connect to the rail level with the vdd rail # Use pen since it is in every type of control logic vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() - in_pos = vector(self.rail_offsets["rbl_bl_delay"].x,vdd_ypos) - mid1 = vector(out_pos.x,in_pos.y) - self.add_wire(self.m1_stack,[out_pos, mid1, in_pos]) + in_pos = vector(self.rail_offsets["rbl_bl_delay"].x, vdd_ypos) + mid1 = vector(out_pos.x, in_pos.y) + self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) self.add_via_center(layers=self.m1_stack, offset=in_pos) - # Input from RBL goes to the delay line for futher delay self.copy_layout_pin(self.delay_inst, "in", "rbl_bl") - def create_clk_buf_row(self): """ Create the multistage and gated clock buffer """ self.clk_buf_inst = self.add_inst(name="clkbuf", mod=self.clk_buf_driver) - self.connect_inst(["clk","clk_buf","vdd","gnd"]) + self.connect_inst(["clk", "clk_buf", "vdd", "gnd"]) - def place_clk_buf_row(self,row): + def place_clk_buf_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.clk_buf_inst, x_offset, row) @@ -512,17 +518,16 @@ class control_logic(design.design): self.add_layout_pin_segment_center(text="clk", layer="m2", start=clk_pos, - end=clk_pos.scale(1,0)) + end=clk_pos.scale(1, 0)) 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) + 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(("m3","via2","m2"),[out_pos, mid1, mid2, bus_pos]) + 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()) @@ -532,40 +537,45 @@ class control_logic(design.design): def create_gated_clk_bar_row(self): self.clk_bar_inst = self.add_inst(name="inv_clk_bar", mod=self.inv) - self.connect_inst(["clk_buf","clk_bar","vdd","gnd"]) + self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"]) 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(["cs", "clk_bar", "gated_clk_bar", "vdd", "gnd"]) - def place_gated_clk_bar_row(self,row): + def place_gated_clk_bar_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.clk_bar_inst, x_offset, row) - x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) + x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row) self.row_end_inst.append(self.gated_clk_bar_inst) 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) + 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]) + mid1 = vector(in_pos.x, out_pos.y) + self.add_path("m1", [out_pos, mid1, in_pos]) # This is the second gate over, so it needs to be on M3 clkbuf_map = zip(["A"], ["cs"]) - self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("m3", "via2", "m2")) + 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()) - # 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, ("m3", "via2", "m2")) + 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()) @@ -573,9 +583,9 @@ class control_logic(design.design): def create_gated_clk_buf_row(self): self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", mod=self.and2) - self.connect_inst(["clk_buf", "cs","gated_clk_buf","vdd","gnd"]) + self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) - def place_gated_clk_buf_row(self,row): + def place_gated_clk_buf_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row) @@ -584,11 +594,13 @@ 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, self.gated_clk_buf_inst, self.rail_offsets, ("m3", "via2", "m2")) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_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_buf_inst.get_pin("Z").center()) @@ -602,7 +614,7 @@ class control_logic(design.design): def place_wlen_row(self, row): x_offset = self.control_x_offset - x_offset = self.place_util(self.wl_en_inst, x_offset, row) + x_offset = self.place_util(self.wl_en_inst, x_offset, row) self.row_end_inst.append(self.wl_en_inst) @@ -623,11 +635,11 @@ class control_logic(design.design): mod=self.p_en_bar_driver) self.connect_inst(["p_en_bar_unbuf", "p_en_bar", "vdd", "gnd"]) - def place_pen_row(self,row): + def place_pen_row(self, row): x_offset = self.control_x_offset - x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row) - x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row) + x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row) + x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row) self.row_end_inst.append(self.p_en_bar_driver_inst) @@ -637,8 +649,8 @@ class control_logic(design.design): 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]) + mid1 = vector(out_pos.x, in_pos.y) + self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar") @@ -656,14 +668,12 @@ class control_logic(design.design): # hence we use rbl_bl_delay as well. self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) - - def place_sen_row(self,row): + def place_sen_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.s_en_gate_inst, x_offset, row) self.row_end_inst.append(self.s_en_gate_inst) - def route_sen(self): @@ -683,7 +693,7 @@ class control_logic(design.design): mod=self.inv) self.connect_inst(["rbl_bl_delay", "rbl_bl_delay_bar", "vdd", "gnd"]) - def place_rbl_delay_row(self,row): + def place_rbl_delay_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.rbl_bl_delay_inv_inst, x_offset, row) @@ -700,11 +710,9 @@ class control_logic(design.design): self.add_via_center(layers=self.m1_stack, offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center()) - rbl_map = zip(["A"], ["rbl_bl_delay"]) self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) - def create_wen_row(self): # input: we (or cs) output: w_en @@ -720,8 +728,7 @@ class control_logic(design.design): # Only drive the writes in the second half of the clock cycle during a write operation. self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) - - def place_wen_row(self,row): + def place_wen_row(self, row): x_offset = self.control_x_offset x_offset = self.place_util(self.w_en_gate_inst, x_offset, row) @@ -750,22 +757,22 @@ class control_logic(design.design): self.connect_inst(inst_pins) def place_dffs(self): - self.ctrl_dff_inst.place(vector(0,0)) + self.ctrl_dff_inst.place(vector(0, 0)) def route_dffs(self): if self.port_type == "rw": dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"]) elif self.port_type == "r": - dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"]) + 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")) # Connect the clock rail to the other clock rail 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, 2 * self.m2_pitch) 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_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_via_center(layers=self.m1_stack, offset=rail_pos) @@ -773,34 +780,31 @@ class control_logic(design.design): if (self.port_type == "rw"): self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") - def get_offset(self,row): + def get_offset(self, row): """ Compute the y-offset and mirroring """ - y_off = row*self.and2.height + y_off = row * self.and2.height if row % 2: y_off += self.and2.height mirror="MX" else: mirror="R0" - return (y_off,mirror) - + return (y_off, mirror) def connect_output(self, inst, pin_name, out_name): """ Create an output pin on the right side from the pin of a given instance. """ out_pin = inst.get_pin(pin_name) - right_pos=out_pin.center() + vector(self.width-out_pin.cx(),0) + right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0) self.add_layout_pin_segment_center(text=out_name, layer="m1", start=out_pin.center(), end=right_pos) - - def route_supply(self): """ Add vdd and gnd to the instance cells """ - max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) + max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) for inst in self.row_end_inst: pins = inst.get_pins("vdd") for pin in pins: @@ -818,16 +822,14 @@ class control_logic(design.design): self.add_power_pin("gnd", pin_loc) 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, "gnd") + 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, "gnd") + 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. + """ This adds some points for easier debugging if LVS goes wrong. These should probably be turned off by default though, since extraction will show these as ports in the extracted netlist. """ @@ -851,74 +853,79 @@ class control_logic(design.design): offset=pin.ll(), height=pin.height(), width=pin.width()) - def get_delays_to_wl(self): """Get the delay (in delay units) of the clk to a wordline in the bitcell array""" debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") self.wl_stage_efforts = self.get_wordline_stage_efforts() - clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts) - total_delay = clk_to_wl_rise + clk_to_wl_fall - debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay)) - return clk_to_wl_rise,clk_to_wl_fall + clk_to_wl_rise, clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts) + total_delay = clk_to_wl_rise + clk_to_wl_fall + debug.info(1, + "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, + clk_to_wl_fall, + total_delay)) + return clk_to_wl_rise, clk_to_wl_fall - def get_wordline_stage_efforts(self): """Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts""" stage_effort_list = [] - #Initial direction of gated_clk_bar signal for this path + # Initial direction of gated_clk_bar signal for this path is_clk_bar_rise = True - #Calculate the load on wl_en within the module and add it to external load + # Calculate the load on wl_en within the module and add it to external load external_cout = self.sram.get_wl_en_cin() - #First stage is the clock buffer + # First stage is the clock buffer stage_effort_list += self.clk_buf_driver.get_stage_efforts(external_cout, is_clk_bar_rise) last_stage_is_rise = stage_effort_list[-1].is_rise - #Then ask the sram for the other path delays (from the bank) + # Then ask the sram for the other path delays (from the bank) stage_effort_list += self.sram.get_wordline_stage_efforts(last_stage_is_rise) return stage_effort_list def get_delays_to_sen(self): - """Get the delay (in delay units) of the clk to a sense amp enable. - This does not incorporate the delay of the replica bitline. + """ + Get the delay (in delay units) of the clk to a sense amp enable. + This does not incorporate the delay of the replica bitline. """ debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") self.sen_stage_efforts = self.get_sa_enable_stage_efforts() clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts) - total_delay = clk_to_sen_rise + clk_to_sen_fall - debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay)) - return clk_to_sen_rise, clk_to_sen_fall + total_delay = clk_to_sen_rise + clk_to_sen_fall + debug.info(1, + "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, + clk_to_sen_fall, + total_delay)) + return clk_to_sen_rise, clk_to_sen_fall def get_sa_enable_stage_efforts(self): """Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list""" stage_effort_list = [] - #Initial direction of clock signal for this path + # Initial direction of clock signal for this path last_stage_rise = True - #First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports. + # First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports. if self.port_type == "rw": stage1_cout = self.replica_bitline.get_en_cin() stage_effort_list += self.and2.get_stage_efforts(stage1_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Replica bitline stage, rbl_in -(rbl)-> pre_s_en + # Replica bitline stage, rbl_in -(rbl)-> pre_s_en stage2_cout = self.sen_and2.get_cin() stage_effort_list += self.replica_bitline.determine_sen_stage_efforts(stage2_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #buffer stage, pre_s_en -(buffer)-> s_en + # buffer stage, pre_s_en -(buffer)-> s_en stage3_cout = self.sram.get_sen_cin() stage_effort_list += self.s_en_driver.get_stage_efforts(stage3_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - return stage_effort_list + return stage_effort_list def get_wl_sen_delays(self): - """Gets a list of the stages and delays in order of their path.""" + """ Gets a list of the stages and delays in order of their path. """ if self.sen_stage_efforts == None or self.wl_stage_efforts == None: debug.error("Model delays not calculated for SRAM.", 1) @@ -927,45 +934,45 @@ class control_logic(design.design): return wl_delays, sen_delays def analytical_delay(self, corner, slew, load): - """Gets the analytical delay from clk input to wl_en output""" + """ Gets the analytical delay from clk input to wl_en output """ stage_effort_list = [] - #Calculate the load on clk_buf_bar - ext_clk_buf_cout = self.sram.get_clk_bar_cin() + # Calculate the load on clk_buf_bar + # ext_clk_buf_cout = self.sram.get_clk_bar_cin() - #Operations logic starts on negative edge - last_stage_rise = False + # Operations logic starts on negative edge + last_stage_rise = False - #First stage(s), clk -(pdriver)-> clk_buf. - #clk_buf_cout = self.replica_bitline.get_en_cin() + # First stage(s), clk -(pdriver)-> clk_buf. + # clk_buf_cout = self.replica_bitline.get_en_cin() clk_buf_cout = 0 stage_effort_list += self.clk_buf_driver.get_stage_efforts(clk_buf_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Second stage, clk_buf -(inv)-> clk_bar + # Second stage, clk_buf -(inv)-> clk_bar clk_bar_cout = self.and2.get_cin() stage_effort_list += self.and2.get_stage_efforts(clk_bar_cout, last_stage_rise) last_stage_rise = stage_effort_list[-1].is_rise - #Third stage clk_bar -(and)-> gated_clk_bar + # Third stage clk_bar -(and)-> gated_clk_bar gated_clk_bar_cin = self.get_gated_clk_bar_cin() stage_effort_list.append(self.inv.get_stage_effort(gated_clk_bar_cin, last_stage_rise)) last_stage_rise = stage_effort_list[-1].is_rise - #Stages from gated_clk_bar -------> wordline + # Stages from gated_clk_bar -------> wordline stage_effort_list += self.get_wordline_stage_efforts() return stage_effort_list def get_clk_buf_cin(self): """ - Get the loads that are connected to the buffered clock. + Get the loads that are connected to the buffered clock. Includes all the DFFs and some logic. """ - #Control logic internal load + # Control logic internal load int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin() - #Control logic external load (in the other parts of the SRAM) + # Control logic external load (in the other parts of the SRAM) ext_clk_buf_cap = self.sram.get_clk_bar_cin() return int_clk_buf_cap + ext_clk_buf_cap @@ -976,7 +983,7 @@ class control_logic(design.design): total_cin = 0 total_cin += self.wl_en_driver.get_cin() if self.port_type == 'rw': - total_cin +=self.and2.get_cin() + total_cin += self.and2.get_cin() return total_cin def graph_exclude_dffs(self): @@ -989,7 +996,7 @@ class control_logic(design.design): def place_util(self, inst, x_offset, row): """ Utility to place a row and compute the next offset """ - (y_offset,mirror)=self.get_offset(row) + (y_offset, mirror) = self.get_offset(row) offset = vector(x_offset, y_offset) inst.place(offset, mirror) - return x_offset+inst.width + return x_offset + inst.width diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index e42c01c0..c30f15b6 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -7,13 +7,13 @@ # import debug import design -from tech import drc,parameter +from tech import parameter from tech import cell_properties as props -from math import log from vector import vector from globals import OPTS from sram_factory import factory + class dff_buf(design.design): """ This is a simple buffered DFF. The output is buffered @@ -107,13 +107,23 @@ class dff_buf(design.design): self.dff_inst.place(vector(0,0)) # Add INV1 to the right - well_spacing = max(self.nwell_space, - self.pwell_space, - self.pwell_to_nwell) - self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active,0)) + well_spacing = 0 + try: + well_spacing = max(well_spacing, self.nwell_space) + except AttributeError: + pass + try: + well_spacing = max(well_spacing, self.pwell_space) + except AttributeError: + pass + try: + well_spacing = max(well_spacing, self.pwell_to_nwell) + except AttributeError: + pass + self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0)) # Add INV2 to the right - self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) + self.inv2_inst.place(vector(self.inv1_inst.rx(), 0)) def route_wires(self): # Route dff q to inv1 a diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 0a54a401..f46849c6 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -11,13 +11,15 @@ import math from sram_factory import factory from vector import vector from globals import OPTS +from errors import drc_error +from tech import cell_properties class hierarchical_decoder(design.design): """ Dynamically generated hierarchical decoder. """ - def __init__(self, name, rows): + def __init__(self, name, num_outputs): design.design.__init__(self, name) self.AND_FORMAT = "DEC_AND_{0}" @@ -25,9 +27,16 @@ class hierarchical_decoder(design.design): self.pre2x4_inst = [] self.pre3x8_inst = [] - (self.cell_height, self.cell_multiple) = self.find_decoder_height() - self.rows = rows - self.num_inputs = math.ceil(math.log(self.rows, 2)) + b = factory.create(module_type="bitcell") + try: + self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple + except AttributeError: + self.cell_multiple = 1 + # For debugging + self.cell_height = self.cell_multiple * b.height + + self.num_outputs = num_outputs + self.num_inputs = math.ceil(math.log(self.num_outputs, 2)) (self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs) self.create_netlist() @@ -35,20 +44,37 @@ class hierarchical_decoder(design.design): self.create_layout() def find_decoder_height(self): + """ + Dead code. This would dynamically determine the bitcell multiple, + but I just decided to hard code it in the tech file if it is not 1 + because a DRC tool would be required even to run in front-end mode. + """ b = factory.create(module_type="bitcell") + # Old behavior - return (b.height, 1) + if OPTS.netlist_only: + return (b.height, 1) # Search for the smallest multiple that works cell_multiple = 1 - while cell_multiple < 3: + while cell_multiple < 5: cell_height = cell_multiple * b.height - and3 = factory.create(module_type="pand3", - height=cell_height) - (drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True) - if drc_errors + lvs_errors == 0: - return (cell_height, cell_multiple) + # debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height)) + try: + and3 = factory.create(module_type="pand3", + height=cell_height) + except drc_error: + # debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height)) + pass + else: + (drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True) + total_errors = drc_errors + lvs_errors + if total_errors == 0: + debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple)) + return (cell_height, cell_multiple) + cell_multiple += 1 + else: debug.error("Couldn't find a valid decoder height multiple.", -1) @@ -63,8 +89,8 @@ class hierarchical_decoder(design.design): self.setup_layout_constants() self.place_pre_decoder() self.place_row_decoder() - self.route_input_rails() - self.route_predecode_rails() + self.route_inputs() + self.route_decoder_bus() self.route_vdd_gnd() self.offset_all_coordinates() self.add_boundary() @@ -118,7 +144,7 @@ class hierarchical_decoder(design.design): def setup_netlist_constants(self): self.predec_groups = [] # This array is a 2D array. - # Distributing vertical rails to different groups. One group belongs to one pre-decoder. + # Distributing vertical bus to different groups. One group belongs to one pre-decoder. # For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will # have total 16 output lines out of these 3 pre-decoders and they will # be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ] @@ -157,39 +183,46 @@ class hierarchical_decoder(design.design): self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8 + # We may have more than one bitcell per decoder row + self.num_rows = math.ceil(self.num_outputs / self.cell_multiple) + # We will place this many final decoders per row + self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows) + # Calculates height and width of row-decoder if (self.num_inputs == 4 or self.num_inputs == 5): nand_width = self.and2.width else: nand_width = self.and3.width - self.internal_routing_width = self.m2_pitch * self.total_number_of_predecoder_outputs - self.row_decoder_height = self.inv.height * self.rows + self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1) + self.row_decoder_height = self.inv.height * self.num_rows - self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch + self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch # Calculates height and width of hierarchical decoder - self.height = self.row_decoder_height + # Add extra pitch for good measure + self.height = max(self.predecoder_height, self.row_decoder_height) + self.m3_pitch self.width = self.input_routing_width + self.predecoder_width \ - + self.internal_routing_width + nand_width + self.inv.width + + self.internal_routing_width \ + + self.decoders_per_row * nand_width + self.inv.width - def route_input_rails(self): - """ Create input rails for the predecoders """ + def route_inputs(self): + """ Create input bus for the predecoders """ # inputs should be as high as the decoders input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height # Find the left-most predecoder min_x = 0 if self.no_of_pre2x4 > 0: - min_x = min(min_x, -self.pre2_4.width) + min_x = min(min_x, self.pre2x4_inst[0].lx()) if self.no_of_pre3x8 > 0: - min_x = min(min_x, -self.pre3_8.width) + min_x = min(min_x, self.pre3x8_inst[0].lx()) 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_rails = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, - offset=input_offset, - names=input_bus_names, - length=input_height) + self.input_bus = self.create_vertical_pin_bus(layer="m2", + pitch=self.m2_pitch, + offset=input_offset, + names=input_bus_names, + length=input_height) self.route_input_to_predecodes() @@ -199,7 +232,7 @@ class hierarchical_decoder(design.design): for i in range(2): index = pre_num * 2 + i - input_pos = self.input_rails["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)] in_name = "in_{}".format(i) decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name) @@ -209,13 +242,13 @@ class hierarchical_decoder(design.design): decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) - self.route_input_rail(decoder_offset, input_offset) + self.route_input_bus(decoder_offset, input_offset) for pre_num in range(self.no_of_pre3x8): for i in range(3): index = pre_num * 3 + i + self.no_of_pre2x4 * 2 - input_pos = self.input_rails["addr_{}".format(index)] + input_pos = self.input_bus["addr_{}".format(index)] in_name = "in_{}".format(i) decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name) @@ -225,10 +258,13 @@ class hierarchical_decoder(design.design): decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height) input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1) - self.route_input_rail(decoder_offset, input_offset) + self.route_input_bus(decoder_offset, input_offset) - def route_input_rail(self, input_offset, output_offset): - """ Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """ + def route_input_bus(self, input_offset, output_offset): + """ + Route a vertical M2 coordinate to another + vertical M2 coordinate to the predecode inputs + """ self.add_via_center(layers=self.m2_stack, offset=input_offset) @@ -242,7 +278,7 @@ class hierarchical_decoder(design.design): for i in range(self.num_inputs): self.add_pin("addr_{0}".format(i), "INPUT") - for j in range(self.rows): + for j in range(self.num_outputs): self.add_pin("decode_{0}".format(j), "OUTPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") @@ -311,18 +347,17 @@ class hierarchical_decoder(design.design): else: base= vector(-self.pre2_4.width, num * self.pre2_4.height) - self.pre2x4_inst[num].place(base) + self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0)) def place_pre3x8(self, num): """ Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """ if (self.num_inputs == 3): offset = vector(-self.pre_3_8.width, 0) - mirror = "R0" else: height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height offset = vector(-self.pre3_8.width, height) - self.pre3x8_inst[num].place(offset) + self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0)) def create_row_decoder(self): """ Create the row-decoder by placing AND2/AND3 and Inverters @@ -339,14 +374,14 @@ class hierarchical_decoder(design.design): if (self.num_inputs == 4 or self.num_inputs == 5): for i in range(len(self.predec_groups[0])): for j in range(len(self.predec_groups[1])): - row = len(self.predec_groups[0]) * j + i - if (row < self.rows): - name = self.AND_FORMAT.format(row) + output = len(self.predec_groups[0]) * j + i + if (output < self.num_outputs): + name = self.AND_FORMAT.format(output) self.and_inst.append(self.add_inst(name=name, mod=self.and2)) pins =["out_{0}".format(i), "out_{0}".format(j + len(self.predec_groups[0])), - "decode_{0}".format(row), + "decode_{0}".format(output), "vdd", "gnd"] self.connect_inst(pins) @@ -355,18 +390,18 @@ class hierarchical_decoder(design.design): for i in range(len(self.predec_groups[0])): for j in range(len(self.predec_groups[1])): for k in range(len(self.predec_groups[2])): - row = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \ - + len(self.predec_groups[0]) * j + i + output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \ + + len(self.predec_groups[0]) * j + i - if (row < self.rows): - name = self.AND_FORMAT.format(row) + if (output < self.num_outputs): + name = self.AND_FORMAT.format(output) self.and_inst.append(self.add_inst(name=name, mod=self.and3)) pins = ["out_{0}".format(i), "out_{0}".format(j + len(self.predec_groups[0])), "out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])), - "decode_{0}".format(row), + "decode_{0}".format(output), "vdd", "gnd"] self.connect_inst(pins) @@ -380,7 +415,10 @@ class hierarchical_decoder(design.design): self.route_decoder() def place_decoder_and_array(self): - """ Add a column of AND gates for final decode """ + """ + Add a column of AND gates for final decode. + This may have more than one decoder per row to match the bitcell height. + """ # Row Decoder AND GATE array for address inputs <5. if (self.num_inputs == 4 or self.num_inputs == 5): @@ -392,9 +430,13 @@ class hierarchical_decoder(design.design): self.place_and_array(and_mod=self.and3) def place_and_array(self, and_mod): - """ Add a column of AND gates for the decoder above the predecoders.""" - - for row in range(self.rows): + """ + Add a column of AND gates for the decoder above the predecoders. + """ + + for inst_index in range(self.num_outputs): + row = math.floor(inst_index / self.decoders_per_row) + dec = inst_index % self.decoders_per_row if ((row % 2) == 0): y_off = and_mod.height * row mirror = "R0" @@ -402,46 +444,52 @@ class hierarchical_decoder(design.design): y_off = and_mod.height * (row + 1) mirror = "MX" - self.and_inst[row].place(offset=[self.internal_routing_width, y_off], - mirror=mirror) + x_off = self.internal_routing_width + dec * and_mod.width + self.and_inst[inst_index].place(offset=vector(x_off, y_off), + mirror=mirror) def route_decoder(self): """ Add the pins. """ - for row in range(self.rows): - z_pin = self.and_inst[row].get_pin("Z") - self.add_layout_pin(text="decode_{0}".format(row), + 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()) - def route_predecode_rails(self): - """ Creates vertical metal 2 rails to connect predecoder and decoder stages.""" + def route_decoder_bus(self): + """ + Creates vertical metal 2 bus to connect predecoder and decoder stages. + """ # This is not needed for inputs <4 since they have no pre/decode stages. if (self.num_inputs >= 4): - input_offset = vector(0.5 * self.m2_width, 0) + # 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_rails = self.create_vertical_pin_bus(layer="m2", - pitch=self.m2_pitch, - offset=input_offset, - names=input_bus_names, - length=self.height) + self.predecode_bus = self.create_vertical_pin_bus(layer="m2", + pitch=self.m2_pitch, + offset=vector(0, 0), + names=input_bus_names, + length=self.height) - self.route_rails_to_predecodes() - self.route_rails_to_decoder() - - def route_rails_to_predecodes(self): - """ Iterates through all of the predecodes and connects to the rails including the offsets """ + self.route_predecodes_to_bus() + self.route_bus_to_decoder() + def route_predecodes_to_bus(self): + """ + Iterates through all of the predecodes + and connects to the rails including the offsets + """ # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre2x4): for i in range(4): predecode_name = "predecode_{}".format(pre_num * 4 + i) out_name = "out_{}".format(i) pin = self.pre2x4_inst[pre_num].get_pin(out_name) - self.route_predecode_rail_m3(predecode_name, pin) + x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch + self.route_predecode_bus_inputs(predecode_name, pin, x_offset) # FIXME: convert to connect_bus for pre_num in range(self.no_of_pre3x8): @@ -449,52 +497,82 @@ class hierarchical_decoder(design.design): predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4) out_name = "out_{}".format(i) pin = self.pre3x8_inst[pre_num].get_pin(out_name) - self.route_predecode_rail_m3(predecode_name, pin) + x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch + self.route_predecode_bus_inputs(predecode_name, pin, x_offset) - def route_rails_to_decoder(self): - """ Use the self.predec_groups to determine the connections to the decoder AND gates. - Inputs of AND2/AND3 gates come from different groups. - For example for these groups [ [0,1,2,3] ,[4,5,6,7], - [8,9,10,11,12,13,14,15] ] the first AND3 inputs are connected to - [0,4,8] and second AND3 is connected to [0,4,9] ........... and the - 128th AND3 is connected to [3,7,15] + def route_bus_to_decoder(self): """ - row_index = 0 + Use the self.predec_groups to determine the connections to the decoder AND gates. + Inputs of AND2/AND3 gates come from different groups. + For example for these groups + [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ] + the first AND3 inputs are connected to [0,4,8], + second AND3 is connected to [0,4,9], + ... + and the 128th AND3 is connected to [3,7,15] + """ + output_index = 0 + 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 (row_index < self.rows): + 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_rail(predecode_name, self.and_inst[row_index].get_pin("A")) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("A"), + row_offset) predecode_name = "predecode_{}".format(index_B) - self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B")) - row_index = row_index + 1 + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("B"), + row_offset + self.m3_pitch) + output_index = output_index + 1 elif (self.num_inputs > 5): for index_C in self.predec_groups[2]: for index_B in self.predec_groups[1]: for index_A in self.predec_groups[0]: # FIXME: convert to connect_bus? - if (row_index < self.rows): + if (output_index < self.num_outputs): + row_index = math.floor(output_index / self.decoders_per_row) + row_remainder = (output_index % self.decoders_per_row) + row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m3_pitch predecode_name = "predecode_{}".format(index_A) - self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A")) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("A"), + row_offset) predecode_name = "predecode_{}".format(index_B) - self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B")) + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("B"), + row_offset + self.m3_pitch) predecode_name = "predecode_{}".format(index_C) - self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("C")) - row_index = row_index + 1 + self.route_predecode_bus_outputs(predecode_name, + self.and_inst[output_index].get_pin("C"), + row_offset + 2 * self.m3_pitch) + output_index = output_index + 1 def route_vdd_gnd(self): - """ Add a pin for each row of vdd/gnd which are must-connects next level up. """ + """ + Add a pin for each row of vdd/gnd which are + must-connects next level up. + """ - # The vias will be placed in the center and right of the cells, respectively. - xoffset = self.and_inst[0].rx() - for num in range(0, self.rows): + # The vias will be placed at the right of the cells. + xoffset = max(x.rx() for x in self.and_inst) + for num in range(0, self.num_outputs): + # Only add the power pin for the 1st in each row + if num % self.decoders_per_row: + continue + for pin_name in ["vdd", "gnd"]: # The nand and inv are the same height rows... supply_pin = self.and_inst[num].get_pin(pin_name) pin_pos = vector(xoffset, supply_pin.cy()) + self.add_path("m1", + [supply_pin.lc(), vector(xoffset, supply_pin.cy())]) self.add_power_pin(name=pin_name, loc=pin_pos) @@ -503,23 +581,42 @@ class hierarchical_decoder(design.design): self.copy_layout_pin(pre, "vdd") self.copy_layout_pin(pre, "gnd") - def route_predecode_rail(self, rail_name, pin): - """ Connect the routing rail to the given metal1 pin """ - rail_pos = vector(self.predecode_rails[rail_name].x, pin.lc().y) - self.add_path("m1", [rail_pos, pin.lc()]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) + def route_predecode_bus_outputs(self, rail_name, pin, y_offset): + """ + Connect the routing rail to the given metal1 pin + using a routing track at the given y_offset + + """ + pin_pos = pin.center() + # If we have a single decoder per row, we can route on M1 + if self.decoders_per_row == 1: + rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y) + self.add_path("m1", [rail_pos, pin_pos]) + self.add_via_center(layers=self.m1_stack, + offset=rail_pos) + # If not, we must route over the decoder cells on M3 + else: + rail_pos = vector(self.predecode_bus[rail_name].x, y_offset) + mid_pos = vector(pin_pos.x, rail_pos.y) + self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos]) + self.add_via_center(layers=self.m2_stack, + offset=rail_pos) + self.add_via_center(layers=self.m1_stack, + offset=pin_pos) - def route_predecode_rail_m3(self, rail_name, pin): - """ Connect the routing rail to the given metal1 pin """ + def route_predecode_bus_inputs(self, rail_name, pin, x_offset): + """ + Connect the routing rail to the given metal1 pin using a jog + to the right of the cell at the given x_offset. + """ # This routes the pin up to the rail, basically, to avoid conflicts. # It would be fixed with a channel router. - mid_point = vector(pin.cx(), pin.cy() + self.inv.height / 2) - rail_pos = vector(self.predecode_rails[rail_name].x, mid_point.y) + pin_pos = pin.center() + mid_point1 = vector(x_offset, pin_pos.y) + mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2) + rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y) + self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos]) self.add_via_center(layers=self.m1_stack, - offset=pin.center()) - self.add_wire(("m3", "via2", "m2"), [rail_pos, mid_point, pin.uc()]) - self.add_via_center(layers=self.m2_stack, offset=rail_pos) def input_load(self): diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 44b0d5a5..6938219a 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -90,17 +90,14 @@ class port_address(design.design): # The pre/post is to access the pin from "outside" the cell to avoid DRCs decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc() driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc() - mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0) - mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1) + mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0) + mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1) self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos]) - - - def add_modules(self): self.row_decoder = factory.create(module_type="decoder", - rows=self.num_rows) + num_outputs=self.num_rows) self.add_mod(self.row_decoder) self.wordline_driver = factory.create(module_type="wordline_driver", @@ -108,11 +105,10 @@ class port_address(design.design): cols=self.num_cols) self.add_mod(self.wordline_driver) - def create_row_decoder(self): """ Create the hierarchical row decoder """ - self.row_decoder_inst = self.add_inst(name="row_decoder", + self.row_decoder_inst = self.add_inst(name="row_decoder", mod=self.row_decoder) temp = [] diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index fbf5223a..bdf10f39 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -699,8 +699,13 @@ class port_data(design.design): bottom_names = self._get_bitline_pins(bot_inst_group, bit) top_names = self._get_bitline_pins(top_inst_group, bit) + if bottom_names[0].layer == "m2": + bitline_dirs = ("H", "V") + elif bottom_names[0].layer == "m1": + bitline_dirs = ("V", "H") + route_map = list(zip(bottom_names, top_names)) - self.create_horizontal_channel_route(route_map, offset, self.m1_stack) + self.create_horizontal_channel_route(route_map, offset, self.m1_stack, bitline_dirs) def connect_bitlines(self, inst1, inst2, num_bits, inst1_bls_template="{inst}_{bit}", diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index d8cb2869..c2f68f9b 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -126,14 +126,16 @@ class sense_amp_array(design.design): for i in range(len(self.local_insts)): inst = self.local_insts[i] + gnd_pin = inst.get_pin("gnd") self.add_power_pin(name="gnd", - loc=inst.get_pin("gnd").center(), - start_layer="m2", + loc=gnd_pin.center(), + start_layer=gnd_pin.layer, vertical=True) - + + vdd_pin = inst.get_pin("vdd") self.add_power_pin(name="vdd", - loc=inst.get_pin("vdd").center(), - start_layer="m2", + loc=vdd_pin.center(), + start_layer=vdd_pin.layer, vertical=True) bl_pin = inst.get_pin(inst.mod.get_bl_names()) diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 324b7415..d1c08e18 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -5,17 +5,14 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -import contact -from tech import drc import debug -import math from vector import vector from sram_factory import factory from globals import OPTS import logical_effort + class single_level_column_mux_array(design.design): """ Dynamically generated column mux array. @@ -56,7 +53,7 @@ class single_level_column_mux_array(design.design): self.add_routing() # Find the highest shapes to determine height before adding well highest = self.find_highest_coords() - self.height = highest.y + self.height = highest.y self.add_layout_pins() self.add_enclosure(self.mux_inst, "pwell") @@ -74,22 +71,18 @@ class single_level_column_mux_array(design.design): self.add_pin("br_out_{}".format(i)) self.add_pin("gnd") - def add_modules(self): self.mux = factory.create(module_type="single_level_column_mux", bitcell_bl=self.bitcell_bl, bitcell_br=self.bitcell_br) self.add_mod(self.mux) - def setup_layout_constants(self): - self.column_addr_size = num_of_inputs = int(self.words_per_row / 2) + self.column_addr_size = int(self.words_per_row / 2) self.width = self.columns * self.mux.width # one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br # one extra route pitch is to space from the sense amp - self.route_height = (self.words_per_row + 3)*self.m1_pitch - - + self.route_height = (self.words_per_row + 3) * self.m1_pitch def create_array(self): self.mux_inst = [] @@ -101,8 +94,8 @@ class single_level_column_mux_array(design.design): self.connect_inst(["bl_{}".format(col_num), "br_{}".format(col_num), - "bl_out_{}".format(int(col_num/self.words_per_row)), - "br_out_{}".format(int(col_num/self.words_per_row)), + "bl_out_{}".format(int(col_num / self.words_per_row)), + "br_out_{}".format(int(col_num / self.words_per_row)), "sel_{}".format(col_num % self.words_per_row), "gnd"]) @@ -117,11 +110,9 @@ class single_level_column_mux_array(design.design): else: mirror = "" - name = "XMUX{0}".format(col_num) offset = vector(xoffset, self.route_height) self.mux_inst[col_num].place(offset=offset, mirror=mirror) - def add_layout_pins(self): """ Add the pins after we determine the height. """ # For every column, add a pass gate @@ -131,18 +122,17 @@ class single_level_column_mux_array(design.design): self.add_layout_pin(text="bl_{}".format(col_num), layer="m2", offset=offset, - height=self.height-offset.y) + height=self.height - offset.y) offset = mux_inst.get_pin("br").ll() self.add_layout_pin(text="br_{}".format(col_num), layer="m2", offset=offset, - height=self.height-offset.y) + height=self.height - offset.y) for inst in self.mux_inst: self.copy_layout_pin(inst, "gnd") - def add_routing(self): self.add_horizontal_input_rail() self.add_vertical_poly_rail() @@ -151,7 +141,7 @@ class single_level_column_mux_array(design.design): def add_horizontal_input_rail(self): """ Create address input rails on M1 below the mux transistors """ for j in range(self.words_per_row): - offset = vector(0, self.route_height + (j-self.words_per_row)*self.m1_pitch) + offset = vector(0, self.route_height + (j - self.words_per_row) * self.m1_pitch) self.add_layout_pin(text="sel_{}".format(j), layer="m1", offset=offset, @@ -167,9 +157,10 @@ class single_level_column_mux_array(design.design): # Add the column x offset to find the right select bit gate_offset = self.mux_inst[col].get_pin("sel").bc() # height to connect the gate to the correct horizontal row - sel_height = self.get_pin("sel_{}".format(sel_index)).by() + # sel_height = self.get_pin("sel_{}".format(sel_index)).by() # use the y offset from the sel pin and the x offset from the gate - offset = vector(gate_offset.x,self.get_pin("sel_{}".format(sel_index)).cy()) + offset = vector(gate_offset.x, + self.get_pin("sel_{}".format(sel_index)).cy()) # Add the poly contact with a shift to account for the rotation self.add_via_center(layers=("m1", "contact", "poly"), offset=offset) @@ -182,11 +173,11 @@ class single_level_column_mux_array(design.design): bl_offset = self.mux_inst[j].get_pin("bl_out").bc() br_offset = self.mux_inst[j].get_pin("br_out").bc() - bl_out_offset = bl_offset - vector(0,(self.words_per_row+1)*self.m1_pitch) - br_out_offset = br_offset - vector(0,(self.words_per_row+2)*self.m1_pitch) + bl_out_offset = bl_offset - vector(0, (self.words_per_row + 1) * self.m1_pitch) + br_out_offset = br_offset - vector(0, (self.words_per_row + 2) * self.m1_pitch) - bl_out_offset_end = bl_out_offset + vector(0,self.route_height) - br_out_offset_end = br_out_offset + vector(0,self.route_height) + bl_out_offset_end = bl_out_offset + vector(0, self.route_height) + br_out_offset_end = br_out_offset + vector(0, self.route_height) if cell_properties.bitcell.mirror.y and j % 2: tmp_bl_out_end = br_out_offset_end @@ -208,21 +199,20 @@ class single_level_column_mux_array(design.design): else: dist = 0 - self.add_path("m1", [bl_out_offset, bl_out_offset+vector(width+dist,0)]) - self.add_path("m1", [br_out_offset, br_out_offset+vector(width-dist,0)]) + self.add_path("m1", [bl_out_offset, bl_out_offset + vector(width + dist, 0)]) + self.add_path("m1", [br_out_offset, br_out_offset + vector(width - dist, 0)]) # Extend the bitline output rails and gnd downward on the first bit of each n-way mux - self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j/self.words_per_row)), + self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)), layer="m2", start=bl_out_offset, end=tmp_bl_out_end) - self.add_layout_pin_segment_center(text="br_out_{}".format(int(j/self.words_per_row)), + self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)), layer="m2", start=br_out_offset, end=tmp_br_out_end) - - # This via is on the right of the wire + # This via is on the right of the wire self.add_via_center(layers=self.m1_stack, offset=bl_out_offset) @@ -231,20 +221,19 @@ class single_level_column_mux_array(design.design): offset=br_out_offset) else: - - self.add_path("m2", [ bl_out_offset, tmp_bl_out_end]) - self.add_path("m2", [ br_out_offset, tmp_br_out_end]) + self.add_path("m2", [bl_out_offset, tmp_bl_out_end]) + self.add_path("m2", [br_out_offset, tmp_br_out_end]) # This via is on the right of the wire self.add_via_center(layers=self.m1_stack, offset=bl_out_offset) - # This via is on the left of the wire + # This via is on the left of the wire self.add_via_center(layers=self.m1_stack, offset=br_out_offset) def get_drain_cin(self): """Get the relative capacitance of the drain of the NMOS pass TX""" from tech import parameter - #Bitcell drain load being used to estimate mux NMOS drain load + # Bitcell drain load being used to estimate mux NMOS drain load drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) - return drain_load + return drain_load diff --git a/compiler/modules/write_driver_array.py b/compiler/modules/write_driver_array.py index d1244aed..ab369dda 100644 --- a/compiler/modules/write_driver_array.py +++ b/compiler/modules/write_driver_array.py @@ -166,7 +166,7 @@ class write_driver_array(design.design): self.add_power_pin(name=n, loc=pin.center(), vertical=True, - start_layer="m2") + start_layer=pin.layer) if self.write_size: for bit in range(self.num_wmasks): inst = self.driver_insts[bit * self.write_size] diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index d83825ea..a942b35f 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -8,10 +8,14 @@ import contact import design import debug -from tech import layer +import math +from bisect import bisect_left +from tech import layer, drc from vector import vector from globals import OPTS +if(OPTS.tech_name == "s8"): + from tech import nmos_bins, pmos_bins, accuracy_requirement class pgate(design.design): """ @@ -282,5 +286,61 @@ 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): + if tx_type == "nmos": + bins = nmos_bins[drc("minwidth_poly")] + elif tx_type == "pmos": + bins = pmos_bins[drc("minwidth_poly")] + else: + debug.error("invalid tx type") + bins = bins[0:bisect_left(bins, target_width) + 1] + if len(bins) == 1: + selected_bin = bins[0] + scaling_factor = math.ceil(target_width / selected_bin) + scaled_bin = bins[0] * scaling_factor + + else: + scaled_bins = [] + scaling_factors = [] + scaled_bins.append(bins[-1]) + scaling_factors.append(1) + for width in bins[0:-1]: + m = math.ceil(target_width / 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] + + debug.info(2, "binning {0} tx, target: {4}, found {1} x {2} = {3}".format(tx_type, selected_bin, scaling_factor, scaled_bin, target_width)) + + return(selected_bin, scaling_factor) + + def permute_widths(self, tx_type, target_width): + + if tx_type == "nmos": + bins = nmos_bins[drc("minwidth_poly")] + elif tx_type == "pmos": + bins = pmos_bins[drc("minwidth_poly")] + else: + debug.error("invalid tx type") + bins = bins[0:bisect_left(bins, target_width) + 1] + if len(bins) == 1: + scaled_bins = [(bins[0], math.ceil(target_width / bins[0]))] + else: + scaled_bins = [] + scaled_bins.append((bins[-1], 1)) + for width in bins[:-1]: + m = math.ceil(target_width / width) + scaled_bins.append((m * width, m)) + + return(scaled_bins) + + def bin_accuracy(self, ideal_width, width): + return abs(1-(ideal_width - width)/ideal_width) \ No newline at end of file diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 3c2e41a2..d3062cba 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -8,14 +8,19 @@ import contact import pgate import debug +import operator from tech import drc, parameter, spice from vector import vector from math import ceil from globals import OPTS from utils import round_to_grid +from bisect import bisect_left import logical_effort from sram_factory import factory +from errors import drc_error +if(OPTS.tech_name == "s8"): + from tech import nmos_bins, pmos_bins, accuracy_requirement class pinv(pgate.pgate): """ @@ -81,6 +86,9 @@ class pinv(pgate.pgate): self.tx_mults = 1 self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") + if OPTS.tech_name == "s8": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) return # Do a quick sanity check and bail if unlikely feasible height @@ -105,11 +113,14 @@ class pinv(pgate.pgate): # 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, - total_height)) + total_height = tx_height + min_channel + 2 * self.top_bottom_space + # debug.check(self.height > total_height, + # "Cell height {0} too small for simple min height {1}.".format(self.height, + # total_height)) + if total_height > self.height: + msg = "Cell height {0} too small for simple min height {1}.".format(self.height, total_height) + raise drc_error(msg) # Determine the height left to the transistors to determine # the number of fingers @@ -127,26 +138,62 @@ class pinv(pgate.pgate): # Determine the number of mults for each to fit width # into available space - self.nmos_width = self.nmos_size * drc("minwidth_tx") - self.pmos_width = self.pmos_size * drc("minwidth_tx") - nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) - pmos_required_mults = max(int(ceil(self.pmos_width / pmos_height_available)), 1) - # The mults must be the same for easy connection of poly - self.tx_mults = max(nmos_required_mults, pmos_required_mults) + if OPTS.tech_name != "s8": + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) + pmos_required_mults = max(int(ceil(self.pmos_width / pmos_height_available)), 1) + # The mults must be the same for easy connection of poly + self.tx_mults = max(nmos_required_mults, pmos_required_mults) - # Recompute each mult width and check it isn't too small - # This could happen if the height is narrow and the size is small - # User should pick a bigger size to fix it... - # We also need to round the width to the grid or we will end up - # with LVS property mismatch errors when fingers are not a grid - # length and get rounded in the offset geometry. - self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults) - debug.check(self.nmos_width >= drc("minwidth_tx"), - "Cannot finger NMOS transistors to fit cell height.") - self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults) - debug.check(self.pmos_width >= drc("minwidth_tx"), - "Cannot finger PMOS transistors to fit cell height.") + # Recompute each mult width and check it isn't too small + # This could happen if the height is narrow and the size is small + # User should pick a bigger size to fix it... + # We also need to round the width to the grid or we will end up + # with LVS property mismatch errors when fingers are not a grid + # length and get rounded in the offset geometry. + self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults) + # debug.check(self.nmos_width >= drc("minwidth_tx"), + # "Cannot finger NMOS transistors to fit cell height.") + if self.nmos_width < drc("minwidth_tx"): + raise drc_error("Cannot finger NMOS transistors to fit cell height.") + self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults) + #debug.check(self.pmos_width >= drc("minwidth_tx"), + # "Cannot finger PMOS transistors to fit cell height.") + if self.pmos_width < drc("minwidth_tx"): + raise drc_error("Cannot finger NMOS transistors to fit cell height.") + else: + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + nmos_bins = self.permute_widths("nmos", self.nmos_width) + pmos_bins = self.permute_widths("pmos", self.pmos_width) + + valid_pmos = [] + for bin in pmos_bins: + if self.bin_accuracy(self.pmos_width, bin[0]) > accuracy_requirement: + valid_pmos.append(bin) + valid_pmos.sort(key = operator.itemgetter(1)) + + valid_nmos = [] + for bin in nmos_bins: + if self.bin_accuracy(self.nmos_width, bin[0]) > accuracy_requirement: + valid_nmos.append(bin) + valid_nmos.sort(key = operator.itemgetter(1)) + + for bin in valid_pmos: + if bin[0]/bin[1] < pmos_height_available: + self.pmos_width = bin[0]/bin[1] + pmos_mults = valid_pmos[0][1] + break + + for bin in valid_nmos: + if bin[0]/bin[1] < nmos_height_available: + self.nmos_width = bin[0]/bin[1] + nmos_mults = valid_pmos[0][1] + break + + self.tx_mults = max(pmos_mults, nmos_mults) def add_ptx(self): """ Create the PMOS and NMOS transistors. """ diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 4a813269..a2dc7d0b 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -9,6 +9,7 @@ import contact import pgate import debug from tech import drc, parameter, spice +from globals import OPTS from vector import vector import logical_effort from sram_factory import factory @@ -37,6 +38,10 @@ class pnand2(pgate.pgate): debug.check(size == 1, "Size 1 pnand2 is only supported now.") self.tx_mults = 1 + if OPTS.tech_name == "s8": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) + # Creates the netlist and layout pgate.pgate.__init__(self, name, height) @@ -98,7 +103,7 @@ class pnand2(pgate.pgate): # 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.m2_space + contact.m2_via.first_layer_width, self.m3_space + contact.m2_via.second_layer_width) @@ -173,13 +178,15 @@ class pnand2(pgate.pgate): 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) - self.add_pwell_contact(self.nmos_nd, self.nmos2_pos) + 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)) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -197,7 +204,7 @@ class pnand2(pgate.pgate): self.nmos2_inst, inputB_yoffset, "B", - position="right") + position="center") # This will help with the wells and the input/output placement self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \ @@ -209,6 +216,7 @@ class pnand2(pgate.pgate): def route_output(self): """ Route the Z output """ + # PMOS1 drain pmos_pin = self.pmos1_inst.get_pin("D") top_pin_offset = pmos_pin.center() @@ -217,29 +225,46 @@ class pnand2(pgate.pgate): bottom_pin_offset = nmos_pin.center() # Output pin - out_offset = vector(nmos_pin.center().x + self.m1_pitch, + c_pin = self.get_pin("B") + out_offset = vector(c_pin.cx() + self.m1_pitch, self.inputA_yoffset) - # Midpoints of the L routes go horizontal first then vertical - mid1_offset = vector(out_offset.x, top_pin_offset.y) + # This routes on M2 + # # Midpoints of the L routes go horizontal first then vertical + # mid1_offset = vector(out_offset.x, top_pin_offset.y) + # mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + + # # Non-preferred active contacts + # self.add_via_center(layers=self.m1_stack, + # directions=("V", "H"), + # offset=pmos_pin.center()) + # # Non-preferred active contacts + # self.add_via_center(layers=self.m1_stack, + # directions=("V", "H"), + # offset=nmos_pin.center()) + # self.add_via_center(layers=self.m1_stack, + # offset=out_offset) + + # # PMOS1 to mid-drain to NMOS2 drain + # self.add_path("m2", + # [top_pin_offset, mid1_offset, out_offset, + # mid2_offset, bottom_pin_offset]) + + # This routes on M1 + # 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) - # Non-preferred active contacts - self.add_via_center(layers=self.m1_stack, - directions=("V", "H"), - offset=pmos_pin.center()) - # Non-preferred active contacts - self.add_via_center(layers=self.m1_stack, - directions=("V", "H"), - offset=nmos_pin.center()) - self.add_via_center(layers=self.m1_stack, - offset=out_offset) + self.add_path("m1", + [top_pin_offset, mid1_offset, out_offset]) + # Route in two segments to have the width rule + self.add_path("m1", + [bottom_pin_offset, mid2_offset + vector(0.5 * self.m1_width, 0)], + width=nmos_pin.height()) + self.add_path("m1", + [mid2_offset, out_offset]) - # PMOS1 to mid-drain to NMOS2 drain - self.add_path("m2", - [top_pin_offset, mid1_offset, out_offset, - mid2_offset, bottom_pin_offset]) - # This extends the output to the edge of the cell self.add_layout_pin_rect_center(text="Z", layer="m1", diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index cc6fd0f8..866d628a 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -12,7 +12,7 @@ from tech import drc, parameter, spice from vector import vector import logical_effort from sram_factory import factory - +from globals import OPTS class pnand3(pgate.pgate): """ @@ -40,6 +40,10 @@ class pnand3(pgate.pgate): "Size 1 pnand3 is only supported now.") self.tx_mults = 1 + if OPTS.tech_name == "s8": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) + # Creates the netlist and layout pgate.pgate.__init__(self, name, height) @@ -190,8 +194,10 @@ 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.pmos3_pos) - self.add_pwell_contact(self.nmos_ns, self.nmos3_pos) + self.add_nwell_contact(self.pmos, + self.pmos3_pos + vector(self.m1_pitch, 0)) + self.add_pwell_contact(self.nmos_ns, + self.nmos3_pos + vector(self.m1_pitch, 0)) def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ @@ -220,18 +226,22 @@ class pnand3(pgate.pgate): self.nmos3_inst, self.inputC_yoffset, "C", - position="center") + position="right") # FIXME: constant hack - self.inputA_yoffset = self.inputB_yoffset + 1.12 * m1_pitch + 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.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A", - position="center") + position="left") def route_output(self): """ Route the Z output """ + # PMOS1 drain pmos1_pin = self.pmos1_inst.get_pin("D") # PMOS3 drain @@ -239,29 +249,56 @@ class pnand3(pgate.pgate): # NMOS3 drain nmos3_pin = self.nmos3_inst.get_pin("D") - # Go up to metal2 for ease on all output pins - self.add_via_center(layers=self.m1_stack, - offset=pmos1_pin.center(), - directions=("V", "V")) - self.add_via_center(layers=self.m1_stack, - offset=pmos3_pin.center(), - directions=("V", "V")) - self.add_via_center(layers=self.m1_stack, - offset=nmos3_pin.center(), - directions=("V", "V")) + # midpoint for routing + mid_offset = vector(nmos3_pin.cx() + self.m1_pitch, + self.inputA_yoffset) + + # Aligned with the well taps + out_offset = vector(self.nwell_contact.cx(), + self.inputA_yoffset) - # PMOS3 and NMOS3 are drain aligned - self.add_path("m2", [pmos3_pin.center(), nmos3_pin.center()]) - # Route in the A input track (top track) - mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) - self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) + # Go up to metal2 for ease on all output pins + # self.add_via_center(layers=self.m1_stack, + # offset=pmos1_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=pmos3_pin.center(), + # directions=("V", "V")) + # self.add_via_center(layers=self.m1_stack, + # offset=nmos3_pin.center(), + # directions=("V", "V")) + + # # Route in the A input track (top track) + # mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) + # self.add_path("m1", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()]) # This extends the output to the edge of the cell - self.add_via_center(layers=self.m1_stack, - offset=mid_offset) + # self.add_via_center(layers=self.m1_stack, + # offset=mid_offset) + + top_left_pin_offset = pmos1_pin.center() + top_right_pin_offset = pmos3_pin.center() + 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]) + # PMOS3 to output + self.add_path("m1", [top_right_pin_offset, + vector(top_right_pin_offset.x, mid_offset.y), + mid_offset]) + # NMOS3 to output + mid2_offset = vector(mid_offset.x, bottom_pin_offset.y) + self.add_path("m1", + [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]) + self.add_layout_pin_rect_center(text="Z", layer="m1", - offset=mid_offset, + offset=out_offset, width=contact.m1_via.first_layer_width, height=contact.m1_via.first_layer_height) diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 75840f26..b1899b4c 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -8,6 +8,7 @@ import contact import pgate import debug +from globals import OPTS from tech import drc, parameter, spice from vector import vector from sram_factory import factory @@ -36,6 +37,10 @@ class pnor2(pgate.pgate): debug.check(size==1, "Size 1 pnor2 is only supported now.") self.tx_mults = 1 + if OPTS.tech_name == "s8": + (self.nmos_width, self.tx_mults) = self.bin_width("nmos", self.nmos_width) + (self.pmos_width, self.tx_mults) = self.bin_width("pmos", self.pmos_width) + # Creates the netlist and layout pgate.pgate.__init__(self, name, height) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index afe14a05..45128421 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -8,6 +8,7 @@ import contact import design import debug +from pgate import pgate from tech import parameter from vector import vector from globals import OPTS @@ -28,6 +29,7 @@ class precharge(design.design): self.bitcell = factory.create(module_type="bitcell") self.beta = parameter["beta"] self.ptx_width = self.beta * parameter["min_tx_size"] + self.ptx_mults = 1 self.width = self.bitcell.width self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br @@ -77,8 +79,11 @@ 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.pmos = factory.create(module_type="ptx", width=self.ptx_width, + mults=self.ptx_mults, tx_type="pmos") self.add_mod(self.pmos) diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 357e1ce4..b4b20381 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -108,7 +108,6 @@ class ptx(design.design): area_sd = 2.5 * self.poly_width * self.tx_width perimeter_sd = 2 * self.poly_width + 2 * self.tx_width if OPTS.tech_name == "s8": - print("here {0}".format(self.name)) # s8 technology is in microns main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type], self.mults, @@ -472,13 +471,13 @@ class ptx(design.design): """Returns an object representing the parameters for delay in tau units.""" # FIXME: Using the same definition as the pinv.py. - parasitic_delay = 1 - size = self.mults*self.tx_width/drc("minwidth_tx") - return logical_effort.logical_effort(self.name, - size, - self.input_load(), - cout, - parasitic_delay) + parasitic_delay = 1 + size = self.mults * self.tx_width / drc("minwidth_tx") + return logical_effort.logical_effort(self.name, + size, + self.input_load(), + cout, + parasitic_delay) def input_load(self): """ @@ -486,7 +485,7 @@ class ptx(design.design): """ # FIXME: this will be applied for the loads of the drain/source - return self.mults*self.tx_width/drc("minwidth_tx") + 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, @@ -497,14 +496,25 @@ class ptx(design.design): well_type=self.well_type) if hasattr(self, "li_stack"): - self.add_via_center(layers=self.li_stack, - offset=pos) - + 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 self.add_layout_pin_rect_center(text=label, layer="m1", offset=pos, - width=contact.mod.second_layer_width, - height=contact.mod.second_layer_height) + width=width, + height=height) + return(contact) def get_cin(self): diff --git a/compiler/tests/04_single_level_column_mux_pbitcell_test.py b/compiler/tests/04_single_level_column_mux_pbitcell_test.py new file mode 100755 index 00000000..6fe55736 --- /dev/null +++ b/compiler/tests/04_single_level_column_mux_pbitcell_test.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +#@unittest.skip("SKIPPING 04_driver_test") + +class single_level_column_mux_pbitcell_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # check single level column mux in multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + factory.reset() + debug.info(2, "Checking column mux for pbitcell (innermost connections)") + tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") + self.local_check(tx) + + factory.reset() + debug.info(2, "Checking column mux for pbitcell (outermost connections)") + tx = factory.create(module_type="single_level_column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") + self.local_check(tx) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py index de5870fc..9f1d03f9 100755 --- a/compiler/tests/04_single_level_column_mux_test.py +++ b/compiler/tests/04_single_level_column_mux_test.py @@ -28,22 +28,6 @@ class single_level_column_mux_test(openram_test): tx = factory.create(module_type="single_level_column_mux", tx_size=8) self.local_check(tx) - # check single level column mux in multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - factory.reset() - debug.info(2, "Checking column mux for pbitcell (innermost connections)") - tx = factory.create(module_type="single_level_column_mux", tx_size=8, bitcell_bl="bl0", bitcell_br="br0") - self.local_check(tx) - - factory.reset() - debug.info(2, "Checking column mux for pbitcell (outermost connections)") - tx = factory.create(module_type="single_level_column_mux",tx_size=8, bitcell_bl="bl2", bitcell_br="br2") - self.local_check(tx) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/06_hierarchical_decoder_pbitcell_test.py b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py index 1fb3a3f6..ec8f4efe 100755 --- a/compiler/tests/06_hierarchical_decoder_pbitcell_test.py +++ b/compiler/tests/06_hierarchical_decoder_pbitcell_test.py @@ -28,39 +28,39 @@ class hierarchical_decoder_test(openram_test): factory.reset() debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=16) + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) self.local_check(a) factory.reset() debug.info(1, "Testing 17 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=17) + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) self.local_check(a) factory.reset() debug.info(1, "Testing 23 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=23) + a = factory.create(module_type="hierarchical_decoder", num_outputs=23) self.local_check(a) debug.info(1, "Testing 32 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=32) + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) self.local_check(a) factory.reset() debug.info(1, "Testing 65 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=65) + a = factory.create(module_type="hierarchical_decoder", num_outputs=65) self.local_check(a) debug.info(1, "Testing 128 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=128) + a = factory.create(module_type="hierarchical_decoder", num_outputs=128) self.local_check(a) factory.reset() debug.info(1, "Testing 341 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=341) + a = factory.create(module_type="hierarchical_decoder", num_outputs=341) self.local_check(a) debug.info(1, "Testing 512 row sample for hierarchical_decoder (multi-port case)") - a = factory.create(module_type="hierarchical_decoder", rows=512) + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py index ab7e844f..7b77c6d9 100755 --- a/compiler/tests/06_hierarchical_decoder_test.py +++ b/compiler/tests/06_hierarchical_decoder_test.py @@ -20,47 +20,38 @@ class hierarchical_decoder_test(openram_test): def runTest(self): config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) globals.init_openram(config_file) - # Doesn't require hierarchical decoder - # debug.info(1, "Testing 4 row sample for hierarchical_decoder") - # a = hierarchical_decoder.hierarchical_decoder(name="hd1, rows=4) - # self.local_check(a) - - # Doesn't require hierarchical decoder - # debug.info(1, "Testing 8 row sample for hierarchical_decoder") - # a = hierarchical_decoder.hierarchical_decoder(name="hd2", rows=8) - # self.local_check(a) # check hierarchical decoder for single port debug.info(1, "Testing 16 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=16) + a = factory.create(module_type="hierarchical_decoder", num_outputs=16) self.local_check(a) debug.info(1, "Testing 17 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=17) + a = factory.create(module_type="hierarchical_decoder", num_outputs=17) self.local_check(a) debug.info(1, "Testing 23 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=23) + a = factory.create(module_type="hierarchical_decoder", num_outputs=23) self.local_check(a) debug.info(1, "Testing 32 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=32) + a = factory.create(module_type="hierarchical_decoder", num_outputs=32) self.local_check(a) debug.info(1, "Testing 65 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=65) + a = factory.create(module_type="hierarchical_decoder", num_outputs=65) self.local_check(a) debug.info(1, "Testing 128 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=128) + a = factory.create(module_type="hierarchical_decoder", num_outputs=128) self.local_check(a) debug.info(1, "Testing 341 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=341) + a = factory.create(module_type="hierarchical_decoder", num_outputs=341) self.local_check(a) debug.info(1, "Testing 512 row sample for hierarchical_decoder") - a = factory.create(module_type="hierarchical_decoder", rows=512) + a = factory.create(module_type="hierarchical_decoder", num_outputs=512) self.local_check(a) globals.end_openram() diff --git a/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py new file mode 100755 index 00000000..8afc45da --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class hierarchical_predecode2x4_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # checking hierarchical precode 2x4 for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") + a = factory.create(module_type="hierarchical_predecode2x4") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py index 2e55f5e9..8bb42253 100755 --- a/compiler/tests/06_hierarchical_predecode2x4_test.py +++ b/compiler/tests/06_hierarchical_predecode2x4_test.py @@ -26,16 +26,6 @@ class hierarchical_predecode2x4_test(openram_test): a = factory.create(module_type="hierarchical_predecode2x4") self.local_check(a) - # checking hierarchical precode 2x4 for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for hierarchy_predecode2x4 (multi-port case)") - a = factory.create(module_type="hierarchical_predecode2x4") - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py new file mode 100755 index 00000000..34d38ddf --- /dev/null +++ b/compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class hierarchical_predecode3x8_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + + # checking hierarchical precode 3x8 for multi-port + OPTS.bitcell = "pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 + + debug.info(1, "Testing sample for hierarchy_predecode3x8 (multi-port case)") + a = factory.create(module_type="hierarchical_predecode3x8") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py index da7573e1..f01fae03 100755 --- a/compiler/tests/06_hierarchical_predecode3x8_test.py +++ b/compiler/tests/06_hierarchical_predecode3x8_test.py @@ -26,16 +26,6 @@ class hierarchical_predecode3x8_test(openram_test): a = factory.create(module_type="hierarchical_predecode3x8") self.local_check(a) - # checking hierarchical precode 3x8 for multi-port - OPTS.bitcell = "pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for hierarchy_predecode3x8 (multi-port case)") - a = factory.create(module_type="hierarchical_predecode3x8") - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/18_port_data_1rw_1r_test.py b/compiler/tests/18_port_data_1rw_1r_test.py new file mode 100755 index 00000000..6201de6a --- /dev/null +++ b/compiler/tests/18_port_data_1rw_1r_test.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +import unittest +from testutils import * +import sys,os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class port_data_1rw_1r_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + from sram_config import sram_config + + OPTS.bitcell = "bitcell_1w_1r" + OPTS.num_rw_ports = 0 + OPTS.num_r_ports = 1 + OPTS.num_w_ports = 1 + + c = sram_config(word_size=4, + num_words=16) + + c.words_per_row=1 + factory.reset() + c.recompute_sizes() + debug.info(1, "No column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=32 + c.words_per_row=2 + factory.reset() + c.recompute_sizes() + debug.info(1, "Two way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.num_words=64 + c.words_per_row=4 + factory.reset() + c.recompute_sizes() + debug.info(1, "Four way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + c.word_size=2 + c.num_words=128 + c.words_per_row=8 + factory.reset() + c.recompute_sizes() + debug.info(1, "Eight way column mux") + a = factory.create("port_data", sram_config=c, port=0) + self.local_check(a) + a = factory.create("port_data", sram_config=c, port=1) + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/18_port_data_test.py b/compiler/tests/18_port_data_test.py index 71681056..57e19846 100755 --- a/compiler/tests/18_port_data_test.py +++ b/compiler/tests/18_port_data_test.py @@ -55,52 +55,6 @@ class port_data_test(openram_test): a = factory.create("port_data", sram_config=c, port=0) self.local_check(a) - OPTS.bitcell = "bitcell_1w_1r" - OPTS.num_rw_ports = 0 - OPTS.num_r_ports = 1 - OPTS.num_w_ports = 1 - - c.num_words=16 - c.words_per_row=1 - factory.reset() - c.recompute_sizes() - debug.info(1, "No column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.num_words=32 - c.words_per_row=2 - factory.reset() - c.recompute_sizes() - debug.info(1, "Two way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.num_words=64 - c.words_per_row=4 - factory.reset() - c.recompute_sizes() - debug.info(1, "Four way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - - c.word_size=2 - c.num_words=128 - c.words_per_row=8 - factory.reset() - c.recompute_sizes() - debug.info(1, "Eight way column mux") - a = factory.create("port_data", sram_config=c, port=0) - self.local_check(a) - a = factory.create("port_data", sram_config=c, port=1) - self.local_check(a) - globals.end_openram() # run the test from the command line diff --git a/compiler/tests/30_openram_back_end_test.py b/compiler/tests/30_openram_back_end_test.py index 73d201b1..591d03ac 100755 --- a/compiler/tests/30_openram_back_end_test.py +++ b/compiler/tests/30_openram_back_end_test.py @@ -51,7 +51,7 @@ class openram_back_end_test(openram_test): debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") exe_name = "{0}/openram.py ".format(OPENRAM_HOME) else: - exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME) cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, @@ -64,33 +64,32 @@ class openram_back_end_test(openram_test): # assert an error until we actually check a resul for extension in ["gds", "v", "lef", "sp"]: - filename = "{0}/{1}.{2}".format(out_path,out_file,extension) - debug.info(1,"Checking for file: " + filename) - self.assertEqual(os.path.exists(filename),True) + filename = "{0}/{1}.{2}".format(out_path, out_file, extension) + debug.info(1, "Checking for file: " + filename) + self.assertEqual(os.path.exists(filename), True) # Make sure there is any .lib file import glob files = glob.glob('{0}/*.lib'.format(out_path)) self.assertTrue(len(files)>0) - # Make sure there is any .html file + # Make sure there is any .html file if os.path.exists(out_path): datasheets = glob.glob('{0}/*html'.format(out_path)) self.assertTrue(len(datasheets)>0) # grep any errors from the output - output_log = open("{0}/output.log".format(out_path),"r") + output_log = open("{0}/output.log".format(out_path), "r") output = output_log.read() output_log.close() - self.assertEqual(len(re.findall('ERROR',output)),0) - self.assertEqual(len(re.findall('WARNING',output)),0) - + self.assertEqual(len(re.findall('ERROR', output)), 0) + self.assertEqual(len(re.findall('WARNING', output)), 0) # now clean up the directory if OPTS.purge_temp: if os.path.exists(out_path): shutil.rmtree(out_path, ignore_errors=True) - self.assertEqual(os.path.exists(out_path),False) + self.assertEqual(os.path.exists(out_path), False) globals.end_openram() diff --git a/compiler/tests/30_openram_front_end_test.py b/compiler/tests/30_openram_front_end_test.py index 3aec10f9..127df6c2 100755 --- a/compiler/tests/30_openram_front_end_test.py +++ b/compiler/tests/30_openram_front_end_test.py @@ -51,7 +51,7 @@ class openram_front_end_test(openram_test): debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") exe_name = "{0}/openram.py ".format(OPENRAM_HOME) else: - exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) config_name = "{0}/tests/configs/config_front_end.py".format(OPENRAM_HOME) cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, @@ -64,33 +64,32 @@ class openram_front_end_test(openram_test): # assert an error until we actually check a result for extension in ["v", "lef", "sp", "gds"]: - filename = "{0}/{1}.{2}".format(out_path,out_file,extension) - debug.info(1,"Checking for file: " + filename) - self.assertEqual(os.path.exists(filename),True) + filename = "{0}/{1}.{2}".format(out_path, out_file, extension) + debug.info(1, "Checking for file: " + filename) + self.assertEqual(os.path.exists(filename), True) # Make sure there is any .lib file import glob files = glob.glob('{0}/*.lib'.format(out_path)) self.assertTrue(len(files)>0) - # Make sure there is any .html file + # Make sure there is any .html file if os.path.exists(out_path): datasheets = glob.glob('{0}/*html'.format(out_path)) self.assertTrue(len(datasheets)>0) # grep any errors from the output - output_log = open("{0}/output.log".format(out_path),"r") + output_log = open("{0}/output.log".format(out_path), "r") output = output_log.read() output_log.close() - self.assertEqual(len(re.findall('ERROR',output)),0) - self.assertEqual(len(re.findall('WARNING',output)),0) + self.assertEqual(len(re.findall('ERROR', output)), 0) + self.assertEqual(len(re.findall('WARNING', output)), 0) - - # now clean up the directory + # now clean up the directory if OPTS.purge_temp: if os.path.exists(out_path): shutil.rmtree(out_path, ignore_errors=True) - self.assertEqual(os.path.exists(out_path),False) + self.assertEqual(os.path.exists(out_path), False) globals.end_openram() diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index c4fd7cac..99d534d0 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -45,21 +45,34 @@ class openram_test(unittest.TestCase): a.gds_write(tempgds) import verify - result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) - if result != 0: + # Run both DRC and LVS even if DRC might fail + # Magic can still extract despite DRC failing, so it might be ok in some techs + # if we ignore things like minimum metal area of pins + drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification) + + # Always run LVS if we are using magic + if "magic" in OPTS.drc_exe or drc_result == 0: + lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) + + # Only allow DRC to fail and LVS to pass if we are using magic + if "magic" in OPTS.drc_exe and lvs_result == 0 and drc_result != 0: + #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) + #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) + #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + debug.warning("DRC failed but LVS passed: {}".format(a.name)) + elif drc_result != 0: #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("DRC failed: {}".format(a.name)) - - - result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) - if result != 0: + + if lvs_result != 0: #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) #debug.info(0,"Archiving failed files to {}.zip".format(zip_file)) #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("LVS mismatch: {}".format(a.name)) + # For debug... #import pdb; pdb.set_trace() if OPTS.purge_temp: