From a12b5d9e6c2e8fa723a2b8ba93c3ebd780caa5ed Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 6 Apr 2020 13:31:31 -0700 Subject: [PATCH 01/11] Split decoder pbitcell tests --- ...hierarchical_predecode2x4_pbitcell_test.py | 41 +++++++++++++++++++ .../06_hierarchical_predecode2x4_test.py | 10 ----- ...hierarchical_predecode3x8_pbitcell_test.py | 41 +++++++++++++++++++ .../06_hierarchical_predecode3x8_test.py | 10 ----- 4 files changed, 82 insertions(+), 20 deletions(-) create mode 100755 compiler/tests/06_hierarchical_predecode2x4_pbitcell_test.py create mode 100755 compiler/tests/06_hierarchical_predecode3x8_pbitcell_test.py 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 From 331a4f4606a7de3c0d1554e85ed9832e8a6eef67 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 09:48:42 -0700 Subject: [PATCH 02/11] Fix wire width bug in short jogs. PEP8 cleanup. --- compiler/base/design.py | 2 +- compiler/base/wire.py | 56 ++++++++++++++++-------- compiler/modules/hierarchical_decoder.py | 1 - 3 files changed, 38 insertions(+), 21 deletions(-) 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/wire.py b/compiler/base/wire.py index bf1daa6a..250f2d87 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -6,18 +6,18 @@ # All rights reserved. # from tech import drc -import debug from wire_path import wire_path from sram_factory import factory + class wire(wire_path): - """ + """ Object metal wire; given the layer type - Add a wire of minimium metal width between a set of points. + Add a wire of minimium metal width between a set of points. The points should be rectilinear to control the bend points. If not, it will always go down first. The points are the center of the wire. - The layer stack is the vertical, contact/via, and horizontal layers, respectively. + The layer stack is the vertical, contact/via, and horizontal layers, respectively. """ def __init__(self, obj, layer_stack, position_list): self.obj = obj @@ -36,6 +36,7 @@ class wire(wire_path): # wires and wire_paths should not be offset to (0,0) def setup_layers(self): + (horiz_layer, via_layer, vert_layer) = self.layer_stack self.via_layer_name = via_layer @@ -47,8 +48,16 @@ class wire(wire_path): via_connect = factory.create(module_type="contact", layer_stack=self.layer_stack, dimensions=(1, 1)) + + # This is used for short connections to avoid via-to-via spacing errors + self.vert_layer_contact_width = max(via_connect.second_layer_width, + via_connect.first_layer_width) + self.horiz_layer_contact_width = max(via_connect.second_layer_height, + via_connect.first_layer_height) + self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width, drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height] + self.pitch = max(self.node_to_node) # create a 1x1 contact def create_vias(self): @@ -56,12 +65,9 @@ class wire(wire_path): self.c=factory.create(module_type="contact", layer_stack=self.layer_stack, dimensions=(1, 1)) - c_width = self.c.width - c_height = self.c.height - - from itertools import tee,islice - nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n)))) - threewise=nwise(self.position_list,3) + from itertools import tee, islice + nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n)))) + threewise = nwise(self.position_list, 3) for (a, offset, c) in list(threewise): # add a exceptions to prevent a via when we don't change directions @@ -72,18 +78,23 @@ class wire(wire_path): self.obj.add_via_center(layers=self.layer_stack, offset=offset) - def create_rectangles(self): - """ + """ Create the actual rectangles on the appropriate layers - using the position list of the corners. + using the position list of the corners. """ pl = self.position_list # position list for index in range(len(pl) - 1): + # Horizontal wire segment if pl[index][0] != pl[index + 1][0]: line_length = pl[index + 1][0] - pl[index][0] + if abs(line_length) < self.pitch: + 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 +102,15 @@ 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, + if abs(line_length) < self.pitch: + 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 +119,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/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index a805e635..f46849c6 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -33,7 +33,6 @@ class hierarchical_decoder(design.design): except AttributeError: self.cell_multiple = 1 # For debugging - # self.cell_multiple = 2 self.cell_height = self.cell_multiple * b.height self.num_outputs = num_outputs From 43fe1ae023bda3a81408d2ccfd73442f0f8c1294 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 11:16:45 -0700 Subject: [PATCH 03/11] Improve pitch computation --- compiler/base/wire.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/compiler/base/wire.py b/compiler/base/wire.py index 250f2d87..2044eb3a 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -6,6 +6,7 @@ # All rights reserved. # from tech import drc +import contact from wire_path import wire_path from sram_factory import factory @@ -57,8 +58,29 @@ class wire(wire_path): self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width, drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height] - self.pitch = max(self.node_to_node) + self.pitch = self.compute_pitch(self.layer_stack) + def compute_pitch(self, layer_stack): + + """ + This is contact direction independent pitch, + i.e. we take the maximum contact dimension + """ + (layer1, via, layer2) = layer_stack + + if layer1 == "poly" or layer1 == "active": + contact1 = getattr(contact, layer1 + "_contact") + else: + contact1 = getattr(contact, layer1 + "_via") + max_contact = max(contact1.width, contact1.height) + + layer1_space = drc["{0}_to_{0}".format(layer1)] + layer2_space = drc["{0}_to_{0}".format(layer2)] + pitch = max_contact + max(layer1_space, layer2_space) + + return pitch + + # create a 1x1 contact def create_vias(self): """ Add a via and corner square at every corner of the path.""" From 1564d6e02b466a815fac7cc66b26bd615e4fb054 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 11:24:28 -0700 Subject: [PATCH 04/11] PEP8 cleanup --- compiler/drc/design_rules.py | 5 +++-- compiler/drc/drc_lut.py | 10 +++++----- compiler/drc/drc_value.py | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) 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 Date: Wed, 15 Apr 2020 14:29:43 -0700 Subject: [PATCH 05/11] PEP8 cleanup --- compiler/modules/control_logic.py | 353 +++++++++++++++--------------- 1 file changed, 180 insertions(+), 173 deletions(-) diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 1612938d..b4c7402e 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -5,18 +5,16 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -from math import log import design -from tech import drc, parameter from tech import cell_properties as props import debug -import contact from sram_factory import factory import math from vector import vector from globals import OPTS import logical_effort + class control_logic(design.design): """ Dynamically generated Control logic for the total SRAM circuit. @@ -29,7 +27,7 @@ class control_logic(design.design): 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.add_comment("word_size {0}".format(word_size)) self.sram=sram self.num_rows = num_rows @@ -37,14 +35,15 @@ class control_logic(design.design): self.word_size = word_size self.port_type = port_type - self.num_cols = word_size*words_per_row - self.num_words = num_rows*words_per_row + self.num_cols = word_size * words_per_row + self.num_words = num_rows * words_per_row self.enable_delay_chain_resizing = False self.inv_parasitic_delay = logical_effort.logical_effort.pinv # Determines how much larger the sen delay should be. Accounts for possible error in model. - self.wl_timing_tolerance = 1 + # FIXME: This should be made a parameter + self.wl_timing_tolerance = 1 self.wl_stage_efforts = None self.sen_stage_efforts = None @@ -67,17 +66,16 @@ class control_logic(design.design): """ Create layout and route between modules """ self.place_instances() self.route_all() - #self.add_lvs_correspondence_points() + # self.add_lvs_correspondence_points() self.add_boundary() self.DRC_LVS() - def add_pins(self): """ Add the pins to the control logic module. """ self.add_pin_list(self.input_list + ["clk"] + self.rbl_list, "INPUT") - self.add_pin_list(self.output_list,"OUTPUT") - self.add_pin("vdd","POWER") - self.add_pin("gnd","GROUND") + self.add_pin_list(self.output_list, "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Add all the required modules """ @@ -101,14 +99,13 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.rbl_driver) - - # clk_buf drives a flop for every address - addr_flops = math.log(self.num_words,2) + math.log(self.words_per_row,2) + # clk_buf drives a flop for every address + addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2) # plus data flops and control flops num_flops = addr_flops + self.word_size + self.num_control_signals # each flop internally has a FO 5 approximately # plus about 5 fanouts for the control logic - clock_fanout = 5*num_flops + 5 + clock_fanout = 5 * num_flops + 5 self.clk_buf_driver = factory.create(module_type="pdriver", fanout=clock_fanout, height=dff_height) @@ -117,7 +114,7 @@ class control_logic(design.design): # We will use the maximum since this same value is used to size the wl_en # and the p_en_bar drivers - max_fanout = max(self.num_rows,self.num_cols) + max_fanout = max(self.num_rows, self.num_cols) # wl_en drives every row in the bank self.wl_en_driver = factory.create(module_type="pdriver", @@ -127,7 +124,7 @@ class control_logic(design.design): # w_en drives every write driver self.wen_and = factory.create(module_type="pand3", - size=self.word_size+8, + size=self.word_size + 8, height=dff_height) self.add_mod(self.wen_and) @@ -137,7 +134,7 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.sen_and3) - # used to generate inverted signals with low fanout + # used to generate inverted signals with low fanout self.inv = factory.create(module_type="pinv", size=1, height=dff_height) @@ -151,7 +148,6 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.p_en_bar_driver) - self.nand2 = factory.create(module_type="pnand2", height=dff_height) self.add_mod(self.nand2) @@ -179,14 +175,14 @@ class control_logic(design.design): # delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic, # bitcell_loads=bitcell_loads) # #Resize if necessary, condition depends on resizing method - # if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): + # if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): # #This resizes to match fall and rise delays, can make the delay chain weird sizes. # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic) # self.replica_bitline = factory.create(module_type="replica_bitline", # delay_fanout_list=stage_list, # bitcell_loads=bitcell_loads) - # #This resizes based on total delay. + # #This resizes based on total delay. # # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic) # # self.replica_bitline = factory.create(module_type="replica_bitline", # # delay_fanout_list=[delay_fanout]*delay_stages, @@ -195,9 +191,10 @@ class control_logic(design.design): # self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing # self.delay_chain_resized = True - debug.check(OPTS.delay_chain_stages%2, "Must use odd number of delay chain stages for inverting delay chain.") + debug.check(OPTS.delay_chain_stages % 2, + "Must use odd number of delay chain stages for inverting delay chain.") self.delay_chain=factory.create(module_type="delay_chain", - fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage]) + fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ]) self.add_mod(self.delay_chain) def get_heuristic_delay_chain_size(self): @@ -219,17 +216,17 @@ class control_logic(design.design): def set_sen_wl_delays(self): """Set delays for wordline and sense amp enable""" - self.wl_delay_rise,self.wl_delay_fall = self.get_delays_to_wl() - self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() - self.wl_delay = self.wl_delay_rise+self.wl_delay_fall - self.sen_delay = self.sen_delay_rise+self.sen_delay_fall + self.wl_delay_rise, self.wl_delay_fall = self.get_delays_to_wl() + self.sen_delay_rise, self.sen_delay_fall = self.get_delays_to_sen() + self.wl_delay = self.wl_delay_rise + self.wl_delay_fall + self.sen_delay = self.sen_delay_rise + self.sen_delay_fall def does_sen_rise_fall_timing_match(self): """Compare the relative rise/fall delays of the sense amp enable and wordline""" self.set_sen_wl_delays() # This is not necessarily more reliable than total delay in some cases. - 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): + 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 From 94eb2afa36156afa193c38d1f47a206a10cf5de7 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 15:29:55 -0700 Subject: [PATCH 06/11] Change to callable DRC rule. Use bottom coordinate for bus offsets. --- compiler/base/hierarchy_layout.py | 81 ++++++++++++++----------------- compiler/base/wire.py | 4 +- 2 files changed, 38 insertions(+), 47 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 63d53d39..e67f8d85 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: @@ -1000,7 +991,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 @@ -1151,8 +1142,8 @@ class layout(): else: self.bounding_box = self.add_rect(layer=boundary_layer, offset=ll, - height=ur.y-ll.y, - width=ur.x-ll.x) + height=ur.y - ll.y, + width=ur.x - ll.x) def add_enclosure(self, insts, layer="nwell"): """ Add a layer that surrounds the given instances. Useful @@ -1171,8 +1162,8 @@ class layout(): self.add_rect(layer=layer, offset=vector(xmin, ymin), - width=xmax-xmin, - height=ymax-ymin) + width=xmax - xmin, + height=ymax - ymin) def copy_power_pins(self, inst, name): """ @@ -1192,7 +1183,7 @@ class layout(): else: debug.warning("{0} pins of {1} should be on {2} or metal1 for "\ "supply router." - .format(name,inst.name,self.pwr_grid_layer)) + .format(name, inst.name, self.pwr_grid_layer)) def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"): """ @@ -1244,8 +1235,8 @@ class layout(): [ll, ur] = bbox supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width - height = (ur.y-ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing - width = (ur.x-ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing + height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing + width = (ur.x - ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing # LEFT vertical rails offset = ll + vector(-2 * self.supply_rail_pitch, diff --git a/compiler/base/wire.py b/compiler/base/wire.py index 2044eb3a..23cc1b41 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -74,8 +74,8 @@ class wire(wire_path): contact1 = getattr(contact, layer1 + "_via") max_contact = max(contact1.width, contact1.height) - layer1_space = drc["{0}_to_{0}".format(layer1)] - layer2_space = drc["{0}_to_{0}".format(layer2)] + layer1_space = drc("{0}_to_{0}".format(layer1)) + layer2_space = drc("{0}_to_{0}".format(layer2)) pitch = max_contact + max(layer1_space, layer2_space) return pitch From 9d2902de9ea6b1c5f575a434a798c30b860cd942 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 15:55:49 -0700 Subject: [PATCH 07/11] Conditional well spacing --- compiler/modules/dff_buf.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) 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 From b347e3f7a8fdaa7289f12bbacbb982cd7e3199d4 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 15 Apr 2020 16:49:04 -0700 Subject: [PATCH 08/11] Try both layers for reversed layer stacks. --- compiler/base/wire.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/base/wire.py b/compiler/base/wire.py index 23cc1b41..ddb10f43 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -71,7 +71,10 @@ class wire(wire_path): if layer1 == "poly" or layer1 == "active": contact1 = getattr(contact, layer1 + "_contact") else: - contact1 = getattr(contact, layer1 + "_via") + try: + contact1 = getattr(contact, layer1 + "_via") + except AttributeError: + contact1 = getattr(contact, layer2 + "_via") max_contact = max(contact1.width, contact1.height) layer1_space = drc("{0}_to_{0}".format(layer1)) From d1319d633d96f68d006e719afa72542cb336bb5f Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 16 Apr 2020 11:02:54 -0700 Subject: [PATCH 09/11] Don't widen too short wires either --- compiler/base/wire.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/base/wire.py b/compiler/base/wire.py index ddb10f43..10272ede 100644 --- a/compiler/base/wire.py +++ b/compiler/base/wire.py @@ -83,7 +83,6 @@ class wire(wire_path): return pitch - # create a 1x1 contact def create_vias(self): """ Add a via and corner square at every corner of the path.""" @@ -113,7 +112,9 @@ class wire(wire_path): # Horizontal wire segment if pl[index][0] != pl[index + 1][0]: line_length = pl[index + 1][0] - pl[index][0] - if abs(line_length) < self.pitch: + # 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.horiz_layer_contact_width: width = self.horiz_layer_contact_width else: width = self.horiz_layer_width @@ -131,7 +132,9 @@ class wire(wire_path): # Vertical wire segment elif pl[index][1] != pl[index + 1][1]: line_length = pl[index + 1][1] - pl[index][1] - if abs(line_length) < self.pitch: + # 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 From 770533e7b1eab1e03bcbdbaaa1901d4cc2ae2de9 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 16 Apr 2020 11:27:26 -0700 Subject: [PATCH 10/11] Parameterize vdd and gnd pin in sense amp array. --- compiler/modules/sense_amp_array.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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()) From 843e9414df9fad170a6926e42b435c9e64928cd8 Mon Sep 17 00:00:00 2001 From: mrg Date: Thu, 16 Apr 2020 11:28:35 -0700 Subject: [PATCH 11/11] Parameterize vdd and gnd pin in write driver array. --- compiler/modules/write_driver_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]