diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 05663b6f..381f01dd 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -14,65 +14,14 @@ from globals import OPTS from base import logical_effort -class control_logic(design): +class control_logic(control_logic_base): """ Dynamically generated Control logic for the total SRAM circuit. """ def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): """ Constructor """ - name = "control_logic_" + port_type - super().__init__(name) - debug.info(1, "Creating {}".format(name)) - self.add_comment("num_rows: {0}".format(num_rows)) - self.add_comment("words_per_row: {0}".format(words_per_row)) - self.add_comment("word_size {0}".format(word_size)) - - self.sram=sram - self.num_rows = num_rows - self.words_per_row = words_per_row - self.word_size = word_size - self.port_type = port_type - - if not spare_columns: - self.num_spare_cols = 0 - else: - self.num_spare_cols = spare_columns - - self.num_cols = word_size * words_per_row + self.num_spare_cols - self.num_words = num_rows * words_per_row - - self.enable_delay_chain_resizing = False - self.inv_parasitic_delay = logical_effort.pinv - - # Determines how much larger the sen delay should be. Accounts for possible error in model. - # FIXME: This should be made a parameter - self.wl_timing_tolerance = 1 - self.wl_stage_efforts = None - self.sen_stage_efforts = None - - if self.port_type == "rw": - self.num_control_signals = 2 - else: - self.num_control_signals = 1 - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.setup_signal_busses() - self.add_pins() - self.add_modules() - self.create_instances() - - def create_layout(self): - """ Create layout and route between modules """ - self.place_instances() - self.route_all() - # self.add_lvs_correspondence_points() - self.add_boundary() - self.DRC_LVS() + super().__init__(num_rows, words_per_row, word_size, spare_columns, sram, port_type, name) def add_pins(self): """ Add the pins to the control logic module. """ @@ -151,93 +100,6 @@ class control_logic(design): self.delay_chain=factory.create(module_type="delay_chain", fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ]) - 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 - debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) - - # 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) - debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") - 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_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)) - - # 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_close = True - safe_fanout_rise = fanout_rise - safe_fanout_fall = fanout_fall - - if stages_fall == stages_rise: - break - 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. - # 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 - 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)] - 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 - # 3 is the minimum delay per stage (with pinv=0). - if required_delay <= 3 + self.inv_parasitic_delay: - return 1 - delay_per_stage = fanout + 1 + self.inv_parasitic_delay - delay_stages = ceil(required_delay / delay_per_stage) - return delay_stages - def setup_signal_busses(self): """ Setup bus names, determine the size of the busses etc """ @@ -277,17 +139,6 @@ class control_logic(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 - # DFF spacing plus the power routing - offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0) - - self.input_bus = self.create_vertical_bus("m2", - offset, - self.internal_bus_list, - height) - def create_instances(self): """ Create all the instances """ self.create_dffs() @@ -373,24 +224,12 @@ class control_logic(design): self.route_supplies() def create_delay(self): - """ Create the replica bitline """ + """ Create the delay chain """ self.delay_inst=self.add_inst(name="delay_chain", mod=self.delay_chain) # 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): - """ Place the replica bitline """ - debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") - - # It is flipped on X axis - y_off = row * self.and2.height + self.delay_chain.height - - # Add the RBL above the rows - # Add to the right of the control rows and routing channel - offset = vector(0, y_off) - self.delay_inst.place(offset, mirror="MX") - def route_delay(self): out_pos = self.delay_inst.get_pin("out").center() @@ -406,109 +245,6 @@ class control_logic(design): # 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"]) - - 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) - - self.row_end_inst.append(self.clk_buf_inst) - - def route_clk_buf(self): - clk_pin = self.clk_buf_inst.get_pin("A") - clk_pos = clk_pin.center() - self.add_layout_pin_rect_center(text="clk", - layer="m2", - offset=clk_pos) - self.add_via_stack_center(from_layer=clk_pin.layer, - to_layer="m2", - offset=clk_pos) - - self.route_output_to_bus_jogged(self.clk_buf_inst, - "clk_buf") - self.connect_output(self.clk_buf_inst, "Z", "clk_buf") - - def create_gated_clk_bar_row(self): - self.clk_bar_inst = self.add_inst(name="inv_clk_bar", - mod=self.inv) - 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(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) - - 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) - - 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.input_bus) - - out_pin = self.clk_bar_inst.get_pin("Z") - out_pos = out_pin.center() - in_pin = self.gated_clk_bar_inst.get_pin("A") - in_pos = in_pin.center() - self.add_zjog(out_pin.layer, out_pos, in_pos) - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer=in_pin.layer, - offset=in_pos) - - - # This is the second gate over, so it needs to be on M3 - clkbuf_map = zip(["B"], ["cs"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_bar_inst, - self.input_bus, - self.m2_stack[::-1]) - # The pin is on M1, so we need another via as well - b_pin = self.gated_clk_bar_inst.get_pin("B") - self.add_via_stack_center(from_layer=b_pin.layer, - to_layer="m3", - offset=b_pin.center()) - - # This is the second gate over, so it needs to be on M3 - self.route_output_to_bus_jogged(self.gated_clk_bar_inst, - "gated_clk_bar") - - def create_gated_clk_buf_row(self): - self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", - mod=self.and2) - self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) - - 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) - - self.row_end_inst.append(self.gated_clk_buf_inst) - - 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.input_bus) - - clkbuf_map = zip(["Z"], ["gated_clk_buf"]) - self.connect_vertical_bus(clkbuf_map, - self.gated_clk_buf_inst, - self.input_bus, - self.m2_stack[::-1]) - # The pin is on M1, so we need another via as well - z_pin = self.gated_clk_buf_inst.get_pin("Z") - self.add_via_stack_center(from_layer=z_pin.layer, - to_layer="m2", - offset=z_pin.center()) - def create_wlen_row(self): # input pre_p_en, output: wl_en self.wl_en_inst=self.add_inst(name="buf_wl_en", @@ -651,194 +387,3 @@ class control_logic(design): self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.input_bus) self.connect_output(self.w_en_gate_inst, "Z", "w_en") - - def create_dffs(self): - self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", - mod=self.ctrl_dff_array) - inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list - self.connect_inst(inst_pins) - - def place_dffs(self): - 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"]) - else: - dff_out_map = zip(["dout_bar_0"], ["cs"]) - self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) - - # Connect the clock rail to the other clock rail - # by routing in the supply rail track to avoid channel conflicts - in_pos = self.ctrl_dff_inst.get_pin("clk").uc() - mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch) - rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) - self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) - self.add_via_center(layers=self.m1_stack, - offset=rail_pos) - - self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") - if (self.port_type == "rw"): - self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") - - def get_offset(self, row): - """ Compute the y-offset and mirroring """ - y_off = row * self.and2.height - if row % 2: - y_off += self.and2.height - mirror="MX" - else: - mirror="R0" - - 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) - out_pos = out_pin.center() - right_pos = out_pos + vector(self.width - out_pin.cx(), 0) - - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer="m2", - offset=out_pos) - self.add_layout_pin_segment_center(text=out_name, - layer="m2", - start=out_pos, - end=right_pos) - - def route_supplies(self): - """ Add vdd and gnd to the instance cells """ - - pin_layer = self.dff.get_pin("vdd").layer - supply_layer = self.supply_stack[2] - - - # FIXME: We should be able to replace this with route_vertical_pins instead - # but we may have to make the logic gates a separate module so that they - # have row pins of the same width - max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) - min_row_x_loc = self.control_x_offset - - vdd_pin_locs = [] - gnd_pin_locs = [] - - last_via = None - for inst in self.row_end_inst: - pins = inst.get_pins("vdd") - for pin in pins: - if pin.layer == pin_layer: - row_loc = pin.rc() - pin_loc = vector(max_row_x_loc, pin.rc().y) - vdd_pin_locs.append(pin_loc) - last_via = self.add_via_stack_center(from_layer=pin_layer, - to_layer=supply_layer, - offset=pin_loc, - min_area=True) - self.add_path(pin_layer, [row_loc, pin_loc]) - - pins = inst.get_pins("gnd") - for pin in pins: - if pin.layer == pin_layer: - row_loc = pin.rc() - pin_loc = vector(min_row_x_loc, pin.rc().y) - gnd_pin_locs.append(pin_loc) - last_via = self.add_via_stack_center(from_layer=pin_layer, - to_layer=supply_layer, - offset=pin_loc, - min_area=True) - self.add_path(pin_layer, [row_loc, pin_loc]) - - if last_via: - via_height=last_via.mod.second_layer_height - via_width=last_via.mod.second_layer_width - else: - via_height=None - via_width=0 - - min_y = min([x.y for x in vdd_pin_locs]) - max_y = max([x.y for x in vdd_pin_locs]) - bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height) - top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height) - self.add_layout_pin_segment_center(text="vdd", - layer=supply_layer, - start=bot_pos, - end=top_pos, - width=via_width) - - min_y = min([x.y for x in gnd_pin_locs]) - max_y = max([x.y for x in gnd_pin_locs]) - bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height) - top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height) - self.add_layout_pin_segment_center(text="gnd", - layer=supply_layer, - start=bot_pos, - end=top_pos, - width=via_width) - - 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") - - def add_lvs_correspondence_points(self): - """ 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. - """ - # pin=self.clk_inv1.get_pin("Z") - # self.add_label_pin(text="clk1_bar", - # layer="m1", - # offset=pin.ll(), - # height=pin.height(), - # width=pin.width()) - - # pin=self.clk_inv2.get_pin("Z") - # self.add_label_pin(text="clk2", - # layer="m1", - # offset=pin.ll(), - # height=pin.height(), - # width=pin.width()) - - pin=self.delay_inst.get_pin("out") - self.add_label_pin(text="out", - layer=pin.layer, - offset=pin.ll(), - height=pin.height(), - width=pin.width()) - - def graph_exclude_dffs(self): - """Exclude dffs from graph as they do not represent critical path""" - - self.graph_inst_exclude.add(self.ctrl_dff_inst) - if self.port_type=="rw" or self.port_type=="w": - self.graph_inst_exclude.add(self.w_en_gate_inst) - - 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) - offset = vector(x_offset, y_offset) - inst.place(offset, mirror) - return x_offset + inst.width - - def route_output_to_bus_jogged(self, inst, name): - # Connect this at the bottom of the buffer - out_pin = inst.get_pin("Z") - out_pos = out_pin.center() - mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height) - mid2 = vector(self.input_bus[name].cx(), mid1.y) - bus_pos = self.input_bus[name].center() - self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) - self.add_via_stack_center(from_layer=out_pin.layer, - to_layer="m2", - offset=out_pos) - - def get_left_pins(self, name): - """ - Return the left side supply pins to connect to a vertical stripe. - """ - return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name)) diff --git a/compiler/modules/control_logic_base.py b/compiler/modules/control_logic_base.py new file mode 100644 index 00000000..419507c4 --- /dev/null +++ b/compiler/modules/control_logic_base.py @@ -0,0 +1,478 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +from base import design +import debug +from sram_factory import factory +import math +from base import vector +from globals import OPTS +from base import logical_effort + + +class control_logic_base(design): + """ + Generic base class for SRAM control logic. + """ + + def __init__(self, num_rows, words_per_row, word_size, spare_columns=None, sram=None, port_type="rw", name=""): + """ Constructor """ + name = "control_logic_" + port_type + super().__init__(name) + debug.info(1, "Creating {}".format(name)) + self.add_comment("num_rows: {0}".format(num_rows)) + self.add_comment("words_per_row: {0}".format(words_per_row)) + self.add_comment("word_size {0}".format(word_size)) + + self.sram=sram + self.num_rows = num_rows + self.words_per_row = words_per_row + self.word_size = word_size + self.port_type = port_type + + if not spare_columns: + self.num_spare_cols = 0 + else: + self.num_spare_cols = spare_columns + + self.num_cols = word_size * words_per_row + self.num_spare_cols + self.num_words = num_rows * words_per_row + + self.enable_delay_chain_resizing = False + self.inv_parasitic_delay = logical_effort.pinv + + # Determines how much larger the sen delay should be. Accounts for possible error in model. + # FIXME: This should be made a parameter + self.wl_timing_tolerance = 1 + self.wl_stage_efforts = None + self.sen_stage_efforts = None + + if self.port_type == "rw": + self.num_control_signals = 2 + else: + self.num_control_signals = 1 + + self.create_netlist() + if not OPTS.netlist_only: + self.create_layout() + + def create_netlist(self): + self.setup_signal_busses() + self.add_pins() + self.add_modules() + self.create_instances() + + def create_layout(self): + """ Create layout and route between modules """ + self.place_instances() + self.route_all() + # self.add_lvs_correspondence_points() + self.add_boundary() + self.DRC_LVS() + + 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 + debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) + + # 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) + debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") + 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_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)) + + # 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_close = True + safe_fanout_rise = fanout_rise + safe_fanout_fall = fanout_fall + + if stages_fall == stages_rise: + break + 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. + # 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 + 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)] + 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 + # 3 is the minimum delay per stage (with pinv=0). + if required_delay <= 3 + self.inv_parasitic_delay: + return 1 + delay_per_stage = fanout + 1 + self.inv_parasitic_delay + delay_stages = ceil(required_delay / delay_per_stage) + return delay_stages + + def route_rails(self): + """ Add the input signal inverted tracks """ + height = self.control_logic_center.y - self.m2_pitch + # DFF spacing plus the power routing + offset = vector(self.ctrl_dff_array.width + self.m4_pitch, 0) + + self.input_bus = self.create_vertical_bus("m2", + offset, + self.internal_bus_list, + height) + + def place_delay(self, row): + """ Place the delay chain """ + debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") + + # It is flipped on X axis + y_off = row * self.and2.height + self.delay_chain.height + + # Add to the right of the control rows and routing channel + offset = vector(0, y_off) + self.delay_inst.place(offset, mirror="MX") + + 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"]) + + 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) + + self.row_end_inst.append(self.clk_buf_inst) + + def route_clk_buf(self): + clk_pin = self.clk_buf_inst.get_pin("A") + clk_pos = clk_pin.center() + self.add_layout_pin_rect_center(text="clk", + layer="m2", + offset=clk_pos) + self.add_via_stack_center(from_layer=clk_pin.layer, + to_layer="m2", + offset=clk_pos) + + self.route_output_to_bus_jogged(self.clk_buf_inst, + "clk_buf") + self.connect_output(self.clk_buf_inst, "Z", "clk_buf") + + def create_gated_clk_bar_row(self): + self.clk_bar_inst = self.add_inst(name="inv_clk_bar", + mod=self.inv) + 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(["clk_bar", "cs", "gated_clk_bar", "vdd", "gnd"]) + + 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) + + 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.input_bus) + + out_pin = self.clk_bar_inst.get_pin("Z") + out_pos = out_pin.center() + in_pin = self.gated_clk_bar_inst.get_pin("A") + in_pos = in_pin.center() + self.add_zjog(out_pin.layer, out_pos, in_pos) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer=in_pin.layer, + offset=in_pos) + + + # This is the second gate over, so it needs to be on M3 + clkbuf_map = zip(["B"], ["cs"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_bar_inst, + self.input_bus, + self.m2_stack[::-1]) + # The pin is on M1, so we need another via as well + b_pin = self.gated_clk_bar_inst.get_pin("B") + self.add_via_stack_center(from_layer=b_pin.layer, + to_layer="m3", + offset=b_pin.center()) + + # This is the second gate over, so it needs to be on M3 + self.route_output_to_bus_jogged(self.gated_clk_bar_inst, + "gated_clk_bar") + + def create_gated_clk_buf_row(self): + self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf", + mod=self.and2) + self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"]) + + 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) + + self.row_end_inst.append(self.gated_clk_buf_inst) + + 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.input_bus) + + clkbuf_map = zip(["Z"], ["gated_clk_buf"]) + self.connect_vertical_bus(clkbuf_map, + self.gated_clk_buf_inst, + self.input_bus, + self.m2_stack[::-1]) + # The pin is on M1, so we need another via as well + z_pin = self.gated_clk_buf_inst.get_pin("Z") + self.add_via_stack_center(from_layer=z_pin.layer, + to_layer="m2", + offset=z_pin.center()) + + def create_dffs(self): + self.ctrl_dff_inst=self.add_inst(name="ctrl_dffs", + mod=self.ctrl_dff_array) + inst_pins = self.input_list + self.dff_output_list + ["clk_buf"] + self.supply_list + self.connect_inst(inst_pins) + + def place_dffs(self): + 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"]) + else: + dff_out_map = zip(["dout_bar_0"], ["cs"]) + self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.input_bus, self.m2_stack[::-1]) + + # Connect the clock rail to the other clock rail + # by routing in the supply rail track to avoid channel conflicts + in_pos = self.ctrl_dff_inst.get_pin("clk").uc() + mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch) + rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) + self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) + self.add_via_center(layers=self.m1_stack, + offset=rail_pos) + + self.copy_layout_pin(self.ctrl_dff_inst, "din_0", "csb") + if (self.port_type == "rw"): + self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web") + + def get_offset(self, row): + """ Compute the y-offset and mirroring """ + y_off = row * self.and2.height + if row % 2: + y_off += self.and2.height + mirror="MX" + else: + mirror="R0" + + 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) + out_pos = out_pin.center() + right_pos = out_pos + vector(self.width - out_pin.cx(), 0) + + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + self.add_layout_pin_segment_center(text=out_name, + layer="m2", + start=out_pos, + end=right_pos) + + def route_supplies(self): + """ Add vdd and gnd to the instance cells """ + + pin_layer = self.dff.get_pin("vdd").layer + supply_layer = self.supply_stack[2] + + + # FIXME: We should be able to replace this with route_vertical_pins instead + # but we may have to make the logic gates a separate module so that they + # have row pins of the same width + max_row_x_loc = max([inst.rx() for inst in self.row_end_inst]) + min_row_x_loc = self.control_x_offset + + vdd_pin_locs = [] + gnd_pin_locs = [] + + last_via = None + for inst in self.row_end_inst: + pins = inst.get_pins("vdd") + for pin in pins: + if pin.layer == pin_layer: + row_loc = pin.rc() + pin_loc = vector(max_row_x_loc, pin.rc().y) + vdd_pin_locs.append(pin_loc) + last_via = self.add_via_stack_center(from_layer=pin_layer, + to_layer=supply_layer, + offset=pin_loc, + min_area=True) + self.add_path(pin_layer, [row_loc, pin_loc]) + + pins = inst.get_pins("gnd") + for pin in pins: + if pin.layer == pin_layer: + row_loc = pin.rc() + pin_loc = vector(min_row_x_loc, pin.rc().y) + gnd_pin_locs.append(pin_loc) + last_via = self.add_via_stack_center(from_layer=pin_layer, + to_layer=supply_layer, + offset=pin_loc, + min_area=True) + self.add_path(pin_layer, [row_loc, pin_loc]) + + if last_via: + via_height=last_via.mod.second_layer_height + via_width=last_via.mod.second_layer_width + else: + via_height=None + via_width=0 + + min_y = min([x.y for x in vdd_pin_locs]) + max_y = max([x.y for x in vdd_pin_locs]) + bot_pos = vector(max_row_x_loc, min_y - 0.5 * via_height) + top_pos = vector(max_row_x_loc, max_y + 0.5 * via_height) + self.add_layout_pin_segment_center(text="vdd", + layer=supply_layer, + start=bot_pos, + end=top_pos, + width=via_width) + + min_y = min([x.y for x in gnd_pin_locs]) + max_y = max([x.y for x in gnd_pin_locs]) + bot_pos = vector(min_row_x_loc, min_y - 0.5 * via_height) + top_pos = vector(min_row_x_loc, max_y + 0.5 * via_height) + self.add_layout_pin_segment_center(text="gnd", + layer=supply_layer, + start=bot_pos, + end=top_pos, + width=via_width) + + 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") + + def add_lvs_correspondence_points(self): + """ 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. + """ + # pin=self.clk_inv1.get_pin("Z") + # self.add_label_pin(text="clk1_bar", + # layer="m1", + # offset=pin.ll(), + # height=pin.height(), + # width=pin.width()) + + # pin=self.clk_inv2.get_pin("Z") + # self.add_label_pin(text="clk2", + # layer="m1", + # offset=pin.ll(), + # height=pin.height(), + # width=pin.width()) + + pin=self.delay_inst.get_pin("out") + self.add_label_pin(text="out", + layer=pin.layer, + offset=pin.ll(), + height=pin.height(), + width=pin.width()) + + def graph_exclude_dffs(self): + """Exclude dffs from graph as they do not represent critical path""" + + self.graph_inst_exclude.add(self.ctrl_dff_inst) + if self.port_type=="rw" or self.port_type=="w": + self.graph_inst_exclude.add(self.w_en_gate_inst) + + 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) + offset = vector(x_offset, y_offset) + inst.place(offset, mirror) + return x_offset + inst.width + + def route_output_to_bus_jogged(self, inst, name): + # Connect this at the bottom of the buffer + out_pin = inst.get_pin("Z") + out_pos = out_pin.center() + mid1 = vector(out_pos.x, out_pos.y - 0.3 * inst.mod.height) + mid2 = vector(self.input_bus[name].cx(), mid1.y) + bus_pos = self.input_bus[name].center() + self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos]) + self.add_via_stack_center(from_layer=out_pin.layer, + to_layer="m2", + offset=out_pos) + + def get_left_pins(self, name): + """ + Return the left side supply pins to connect to a vertical stripe. + """ + return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name))