diff --git a/compiler/base/delay_data.py b/compiler/base/delay_data.py new file mode 100644 index 00000000..e3d5a8bc --- /dev/null +++ b/compiler/base/delay_data.py @@ -0,0 +1,43 @@ +# 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. +# + +class delay_data(): + """ + This is the delay class to represent the delay information + Time is 50% of the signal to 50% of reference signal delay. + Slew is the 10% of the signal to 90% of signal + """ + def __init__(self, delay=0.0, slew=0.0): + """ init function support two init method""" + # will take single input as a coordinate + self.delay = delay + self.slew = slew + + def __str__(self): + """ override print function output """ + return "Delay Data: Delay "+str(self.delay)+", Slew "+str(self.slew)+"" + + def __add__(self, other): + """ + Override - function (left), for delay_data: a+b != b+a + """ + assert isinstance(other,delay_data) + return delay_data(other.delay + self.delay, + other.slew) + + def __radd__(self, other): + """ + Override - function (right), for delay_data: a+b != b+a + """ + assert isinstance(other,delay_data) + return delay_data(other.delay + self.delay, + self.slew) + + + + diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index b6ac4677..7cf8ee6f 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -10,8 +10,9 @@ from vector import vector from pin_layout import pin_layout class timing_graph(): - """Implements a directed graph - Nodes are currently just Strings. + """ + Implements a directed graph + Nodes are currently just Strings. """ def __init__(self): @@ -20,30 +21,34 @@ class timing_graph(): def add_edge(self, src_node, dest_node): """Adds edge to graph. Nodes added as well if they do not exist.""" + src_node = src_node.lower() dest_node = dest_node.lower() self.graph[src_node].add(dest_node) def add_node(self, node): """Add node to graph with no edges""" + node = node.lower() if not node in self.graph: self.graph[node] = set() def remove_edges(self, node): + """Helper function to remove edges, useful for removing vdd/gnd""" node = node.lower() self.graph[node] = set() - def get_all_paths(self, src_node, dest_node, rmv_rail_nodes=True): + def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True): """Traverse all paths from source to destination""" + src_node = src_node.lower() dest_node = dest_node.lower() - #Remove vdd and gnd by default - #Will require edits if separate supplies are implemented. - if rmv_rail_nodes: - #Names are also assumed. + # Remove vdd and gnd by default + # Will require edits if separate supplies are implemented. + if remove_rail_nodes: + # Names are also assumed. self.remove_edges('vdd') self.remove_edges('gnd') @@ -57,11 +62,20 @@ class timing_graph(): # Call the recursive helper function to print all paths self.get_all_paths_util(src_node, dest_node, visited, path) debug.info(2, "Paths found={}".format(len(self.all_paths))) - + + if reduce_paths: + self.reduce_paths() + return self.all_paths + + def reduce_paths(self): + """ Remove any path that is a subset of another path """ + + self.all_paths = [p1 for p1 in self.all_paths if not any(set(p1)<=set(p2) for p2 in self.all_paths if p1 is not p2)] def get_all_paths_util(self, cur_node, dest_node, visited, path): """Recursive function to find all paths in a Depth First Search manner""" + # Mark the current node as visited and store in path visited.add(cur_node) path.append(cur_node) @@ -72,7 +86,7 @@ class timing_graph(): self.all_paths.append(copy.deepcopy(path)) else: # If current vertex is not destination - #Recur for all the vertices adjacent to this vertex + # Recur for all the vertices adjacent to this vertex for node in self.graph[cur_node]: if node not in visited: self.get_all_paths_util(node, dest_node, visited, path) @@ -83,4 +97,5 @@ class timing_graph(): def __str__(self): """ override print function output """ - return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) \ No newline at end of file + + return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index e106cb2a..2dcc3d73 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -10,6 +10,9 @@ import re import os import math import tech +from delay_data import * +from wire_spice_model import * +from power_data import * class spice(): @@ -25,6 +28,7 @@ class spice(): def __init__(self, name): self.name = name + self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"] # Holds subckts/mods for this module self.mods = [] # Holds the pins for this module @@ -49,27 +53,32 @@ class spice(): def add_comment(self, comment): """ Add a comment to the spice file """ + try: self.commments except: self.comments = [] - else: - self.comments.append(comment) + + self.comments.append(comment) def add_pin(self, name, pin_type="INOUT"): """ Adds a pin to the pins list. Default type is INOUT signal. """ self.pins.append(name) self.pin_type[name]=pin_type + debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type)) - def add_pin_list(self, pin_list, pin_type_list="INOUT"): + def add_pin_list(self, pin_list, pin_type="INOUT"): """ Adds a pin_list to the pins list """ # The type list can be a single type for all pins # or a list that is the same length as the pin list. - if type(pin_type_list)==str: + if type(pin_type)==str: for pin in pin_list: - self.add_pin(pin,pin_type_list) - elif len(pin_type_list)==len(pin_list): - for (pin,ptype) in zip(pin_list, pin_type_list): + debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,pin_type)) + self.add_pin(pin,pin_type) + + elif len(pin_type)==len(pin_list): + for (pin,ptype) in zip(pin_list, pin_type): + debug.check(ptype in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,ptype)) self.add_pin(pin,ptype) else: debug.error("Mismatch in type and pin list lengths.", -1) @@ -87,7 +96,9 @@ class spice(): def get_pin_type(self, name): """ Returns the type of the signal pin. """ - return self.pin_type[name] + pin_type = self.pin_type[name] + debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type)) + return pin_type def get_pin_dir(self, name): """ Returns the direction of the pin. (Supply/ground are INOUT). """ @@ -238,9 +249,12 @@ class spice(): sp.write("\n.SUBCKT {0} {1}\n".format(self.name, " ".join(self.pins))) + for pin in self.pins: + sp.write("* {1:6}: {0} \n".format(pin,self.pin_type[pin])) + for line in self.comments: sp.write("* {}\n".format(line)) - + # every instance must have a set of connections, even if it is empty. if len(self.insts)!=len(self.conns): debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name, @@ -383,102 +397,3 @@ class spice(): def return_power(self, dynamic=0.0, leakage=0.0): return power_data(dynamic, leakage) -class delay_data: - """ - This is the delay class to represent the delay information - Time is 50% of the signal to 50% of reference signal delay. - Slew is the 10% of the signal to 90% of signal - """ - def __init__(self, delay=0.0, slew=0.0): - """ init function support two init method""" - # will take single input as a coordinate - self.delay = delay - self.slew = slew - - def __str__(self): - """ override print function output """ - return "Delay Data: Delay "+str(self.delay)+", Slew "+str(self.slew)+"" - - def __add__(self, other): - """ - Override - function (left), for delay_data: a+b != b+a - """ - assert isinstance(other,delay_data) - return delay_data(other.delay + self.delay, - other.slew) - - def __radd__(self, other): - """ - Override - function (right), for delay_data: a+b != b+a - """ - assert isinstance(other,delay_data) - return delay_data(other.delay + self.delay, - self.slew) - -class power_data: - """ - This is the power class to represent the power information - Dynamic and leakage power are stored as a single object with this class. - """ - def __init__(self, dynamic=0.0, leakage=0.0): - """ init function support two init method""" - # will take single input as a coordinate - self.dynamic = dynamic - self.leakage = leakage - - def __str__(self): - """ override print function output """ - return "Power Data: Dynamic "+str(self.dynamic)+", Leakage "+str(self.leakage)+" in nW" - - def __add__(self, other): - """ - Override - function (left), for power_data: a+b != b+a - """ - assert isinstance(other,power_data) - return power_data(other.dynamic + self.dynamic, - other.leakage + self.leakage) - - def __radd__(self, other): - """ - Override - function (left), for power_data: a+b != b+a - """ - assert isinstance(other,power_data) - return power_data(other.dynamic + self.dynamic, - other.leakage + self.leakage) - - -class wire_spice_model: - """ - This is the spice class to represent a wire - """ - def __init__(self, lump_num, wire_length, wire_width): - self.lump_num = lump_num # the number of segment the wire delay has - self.wire_c = self.cal_wire_c(wire_length, wire_width) # c in each segment - self.wire_r = self.cal_wire_r(wire_length, wire_width) # r in each segment - - def cal_wire_c(self, wire_length, wire_width): - from tech import spice - total_c = spice["wire_unit_c"] * wire_length * wire_width - wire_c = total_c / self.lump_num - return wire_c - - def cal_wire_r(self, wire_length, wire_width): - from tech import spice - total_r = spice["wire_unit_r"] * wire_length / wire_width - wire_r = total_r / self.lump_num - return wire_r - - def return_input_cap(self): - return 0.5 * self.wire_c * self.lump_num - - def return_delay_over_wire(self, slew, swing = 0.5): - # delay will be sum of arithmetic sequence start from - # rc to self.lump_num*rc with step of rc - - swing_factor = abs(math.log(1-swing)) # time constant based on swing - sum_factor = (1+self.lump_num) * self.lump_num * 0.5 # sum of the arithmetic sequence - delay = sum_factor * swing_factor * self.wire_r * self.wire_c - slew = delay * 2 + slew - result= delay_data(delay, slew) - return result - diff --git a/compiler/base/power_data.py b/compiler/base/power_data.py new file mode 100644 index 00000000..77d50d34 --- /dev/null +++ b/compiler/base/power_data.py @@ -0,0 +1,38 @@ +# 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. +# + +class power_data(): + """ + This is the power class to represent the power information + Dynamic and leakage power are stored as a single object with this class. + """ + def __init__(self, dynamic=0.0, leakage=0.0): + """ init function support two init method""" + # will take single input as a coordinate + self.dynamic = dynamic + self.leakage = leakage + + def __str__(self): + """ override print function output """ + return "Power Data: Dynamic "+str(self.dynamic)+", Leakage "+str(self.leakage)+" in nW" + + def __add__(self, other): + """ + Override - function (left), for power_data: a+b != b+a + """ + assert isinstance(other,power_data) + return power_data(other.dynamic + self.dynamic, + other.leakage + self.leakage) + + def __radd__(self, other): + """ + Override - function (left), for power_data: a+b != b+a + """ + assert isinstance(other,power_data) + return power_data(other.dynamic + self.dynamic, + other.leakage + self.leakage) diff --git a/compiler/base/wire_spice_model.py b/compiler/base/wire_spice_model.py new file mode 100644 index 00000000..5624b575 --- /dev/null +++ b/compiler/base/wire_spice_model.py @@ -0,0 +1,42 @@ +# 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. +# + +class wire_spice_model(): + """ + This is the spice class to represent a wire + """ + def __init__(self, lump_num, wire_length, wire_width): + self.lump_num = lump_num # the number of segment the wire delay has + self.wire_c = self.cal_wire_c(wire_length, wire_width) # c in each segment + self.wire_r = self.cal_wire_r(wire_length, wire_width) # r in each segment + + def cal_wire_c(self, wire_length, wire_width): + from tech import spice + total_c = spice["wire_unit_c"] * wire_length * wire_width + wire_c = total_c / self.lump_num + return wire_c + + def cal_wire_r(self, wire_length, wire_width): + from tech import spice + total_r = spice["wire_unit_r"] * wire_length / wire_width + wire_r = total_r / self.lump_num + return wire_r + + def return_input_cap(self): + return 0.5 * self.wire_c * self.lump_num + + def return_delay_over_wire(self, slew, swing = 0.5): + # delay will be sum of arithmetic sequence start from + # rc to self.lump_num*rc with step of rc + + swing_factor = abs(math.log(1-swing)) # time constant based on swing + sum_factor = (1+self.lump_num) * self.lump_num * 0.5 # sum of the arithmetic sequence + delay = sum_factor * swing_factor * self.wire_r * self.wire_c + slew = delay * 2 + slew + result= delay_data(delay, slew) + return result diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 069d42a7..96432cea 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -42,34 +42,41 @@ class bitcell(design.design): cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. return logical_effort.logical_effort('bitline', size, cin, load, parasitic_delay, False) - def list_all_wl_names(self): + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ row_pins = ["wl"] return row_pins - def list_all_bitline_names(self): + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ column_pins = ["bl", "br"] return column_pins - def list_all_bl_names(self): + def get_all_bl_names(self): """ Creates a list of all bl pins names """ column_pins = ["bl"] return column_pins - def list_all_br_names(self): + def get_all_br_names(self): """ Creates a list of all br pins names """ column_pins = ["br"] return column_pins - def get_bl_name(self): + def get_bl_name(self, port=0): """Get bl name""" + debug.check(port==0,"One port for bitcell only.") return "bl" - def get_br_name(self): + def get_br_name(self, port=0): """Get bl name""" + debug.check(port==0,"One port for bitcell only.") return "br" - + + def get_wl_name(self, port=0): + """Get wl name""" + debug.check(port==0,"One port for bitcell only.") + return "wl" + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -96,4 +103,4 @@ class bitcell(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index bd72c61a..f627a4ac 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -43,7 +43,7 @@ class bitcell_1rw_1r(design.design): read_port_load = 0.5 #min size NMOS gate load return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False) - def list_bitcell_pins(self, col, row): + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = ["bl0_{0}".format(col), "br0_{0}".format(col), @@ -55,53 +55,60 @@ class bitcell_1rw_1r(design.design): "gnd"] return bitcell_pins - def list_all_wl_names(self): + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ row_pins = ["wl0", "wl1"] return row_pins - def list_all_bitline_names(self): + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ column_pins = ["bl0", "br0", "bl1", "br1"] return column_pins - def list_all_bl_names(self): + def get_all_bl_names(self): """ Creates a list of all bl pins names """ column_pins = ["bl0", "bl1"] return column_pins - def list_all_br_names(self): + def get_all_br_names(self): """ Creates a list of all br pins names """ column_pins = ["br0", "br1"] return column_pins - def list_read_bl_names(self): + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ column_pins = ["bl0", "bl1"] return column_pins - def list_read_br_names(self): + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ column_pins = ["br0", "br1"] return column_pins - def list_write_bl_names(self): + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ column_pins = ["bl0"] return column_pins - def list_write_br_names(self): + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" column_pins = ["br0"] return column_pins def get_bl_name(self, port=0): """Get bl name by port""" + debug.check(port<2,"Two ports for bitcell_1rw_1r only.") return "bl{}".format(port) def get_br_name(self, port=0): """Get bl name by port""" + debug.check(port<2,"Two ports for bitcell_1rw_1r only.") return "br{}".format(port) + + def get_wl_name(self, port=0): + """Get wl name by port""" + debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + return "wl{}".format(port) def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index 13669dcb..6063cf86 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -43,7 +43,7 @@ class bitcell_1w_1r(design.design): read_port_load = 0.5 #min size NMOS gate load return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False) - def list_bitcell_pins(self, col, row): + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = ["bl0_{0}".format(col), "br0_{0}".format(col), @@ -55,42 +55,42 @@ class bitcell_1w_1r(design.design): "gnd"] return bitcell_pins - def list_all_wl_names(self): + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ row_pins = ["wl0", "wl1"] return row_pins - def list_all_bitline_names(self): + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ column_pins = ["bl0", "br0", "bl1", "br1"] return column_pins - def list_all_bl_names(self): + def get_all_bl_names(self): """ Creates a list of all bl pins names """ column_pins = ["bl0", "bl1"] return column_pins - def list_all_br_names(self): + def get_all_br_names(self): """ Creates a list of all br pins names """ column_pins = ["br0", "br1"] return column_pins - def list_read_bl_names(self): + def get_read_bl_names(self): """ Creates a list of bl pin names associated with read ports """ column_pins = ["bl0", "bl1"] return column_pins - def list_read_br_names(self): + def get_read_br_names(self): """ Creates a list of br pin names associated with read ports """ column_pins = ["br0", "br1"] return column_pins - def list_write_bl_names(self): + def get_write_bl_names(self): """ Creates a list of bl pin names associated with write ports """ column_pins = ["bl0"] return column_pins - def list_write_br_names(self): + def get_write_br_names(self): """ Creates a list of br pin names asscociated with write ports""" column_pins = ["br0"] return column_pins @@ -103,6 +103,11 @@ class bitcell_1w_1r(design.design): """Get bl name by port""" return "br{}".format(port) + def get_wl_name(self, port=0): + """Get wl name by port""" + debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + return "wl{}".format(port) + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -134,6 +139,6 @@ class bitcell_1w_1r(design.design): pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} #Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/dummy_bitcell_1rw_1r.py b/compiler/bitcells/dummy_bitcell_1rw_1r.py new file mode 100644 index 00000000..f8986f2d --- /dev/null +++ b/compiler/bitcells/dummy_bitcell_1rw_1r.py @@ -0,0 +1,45 @@ +# 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 design +import debug +import utils +from tech import GDS,layer,drc,parameter + +class dummy_bitcell_1rw_1r(design.design): + """ + A single bit cell which is forced to store a 0. + This module implements the single memory cell used in the design. It + is a hand-made cell, so the layout and netlist should be available in + the technology library. """ + + pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + (width,height) = utils.get_libcell_size("dummy_cell_1rw_1r", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_1rw_1r", GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + design.design.__init__(self, "dummy_cell_1rw_1r") + debug.info(2, "Create dummy bitcell 1rw+1r object") + + self.width = dummy_bitcell_1rw_1r.width + self.height = dummy_bitcell_1rw_1r.height + self.pin_map = dummy_bitcell_1rw_1r.pin_map + self.add_pin_types(self.type_list) + + def get_wl_cin(self): + """Return the relative capacitance of the access transistor gates""" + #This is a handmade cell so the value must be entered in the tech.py file or estimated. + #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. + #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. + access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] + return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Dummy bitcells are cannot form a path and be part of the timing graph""" + return diff --git a/compiler/bitcells/dummy_bitcell_1w_1r.py b/compiler/bitcells/dummy_bitcell_1w_1r.py new file mode 100644 index 00000000..ef451b8c --- /dev/null +++ b/compiler/bitcells/dummy_bitcell_1w_1r.py @@ -0,0 +1,45 @@ +# 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 design +import debug +import utils +from tech import GDS,layer,drc,parameter + +class dummy_bitcell_1w_1r(design.design): + """ + A single bit cell which is forced to store a 0. + This module implements the single memory cell used in the design. It + is a hand-made cell, so the layout and netlist should be available in + the technology library. """ + + pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] + (width,height) = utils.get_libcell_size("dummy_cell_1w_1r", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_1w_1r", GDS["unit"]) + + def __init__(self, name=""): + # Ignore the name argument + design.design.__init__(self, "dummy_cell_1w_1r") + debug.info(2, "Create dummy bitcell 1w+1r object") + + self.width = dummy_bitcell_1w_1r.width + self.height = dummy_bitcell_1w_1r.height + self.pin_map = dummy_bitcell_1w_1r.pin_map + self.add_pin_types(self.type_list) + + def get_wl_cin(self): + """Return the relative capacitance of the access transistor gates""" + #This is a handmade cell so the value must be entered in the tech.py file or estimated. + #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. + #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. + access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] + return 2*access_tx_cin + + def build_graph(self, graph, inst_name, port_nets): + """Dummy bitcells are cannot form a path and be part of the timing graph""" + return diff --git a/compiler/bitcells/dummy_pbitcell.py b/compiler/bitcells/dummy_pbitcell.py new file mode 100644 index 00000000..ee15e03c --- /dev/null +++ b/compiler/bitcells/dummy_pbitcell.py @@ -0,0 +1,91 @@ +# 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 debug +import design +from tech import drc, spice,parameter +from vector import vector +from globals import OPTS +from sram_factory import factory + +class dummy_pbitcell(design.design): + """ + Creates a replica bitcell using pbitcell + """ + + def __init__(self, name): + self.num_rw_ports = OPTS.num_rw_ports + self.num_w_ports = OPTS.num_w_ports + self.num_r_ports = OPTS.num_r_ports + self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports + + design.design.__init__(self, name) + debug.info(1, "create a dummy bitcell using pbitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, + self.num_w_ports, + self.num_r_ports)) + + self.create_netlist() + self.create_layout() + + def create_netlist(self): + self.add_pins() + self.add_modules() + self.create_modules() + + def create_layout(self): + self.place_pbitcell() + self.route_rbc_connections() + self.DRC_LVS() + + def add_pins(self): + for port in range(self.total_ports): + self.add_pin("bl{}".format(port)) + self.add_pin("br{}".format(port)) + + for port in range(self.total_ports): + self.add_pin("wl{}".format(port)) + + self.add_pin("vdd") + self.add_pin("gnd") + + def add_modules(self): + self.prbc = factory.create(module_type="pbitcell",dummy_bitcell=True) + self.add_mod(self.prbc) + + self.height = self.prbc.height + self.width = self.prbc.width + + def create_modules(self): + self.prbc_inst = self.add_inst(name="pbitcell", + mod=self.prbc) + + temp = [] + for port in range(self.total_ports): + temp.append("bl{}".format(port)) + temp.append("br{}".format(port)) + for port in range(self.total_ports): + temp.append("wl{}".format(port)) + temp.append("vdd") + temp.append("gnd") + self.connect_inst(temp) + + def place_pbitcell(self): + self.prbc_inst.place(offset=vector(0,0)) + + def route_rbc_connections(self): + for port in range(self.total_ports): + self.copy_layout_pin(self.prbc_inst, "bl{}".format(port)) + self.copy_layout_pin(self.prbc_inst, "br{}".format(port)) + for port in range(self.total_ports): + self.copy_layout_pin(self.prbc_inst, "wl{}".format(port)) + self.copy_layout_pin(self.prbc_inst, "vdd") + self.copy_layout_pin(self.prbc_inst, "gnd") + + def get_wl_cin(self): + """Return the relative capacitance of the access transistor gates""" + #This module is made using a pbitcell. Get the cin from that module + return self.prbc.get_wl_cin() diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index eb44a27a..3c59a9e3 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -20,21 +20,30 @@ class pbitcell(design.design): with a variable number of read/write, write, and read ports """ - def __init__(self, name, replica_bitcell=False): + def __init__(self, name, replica_bitcell=False, dummy_bitcell=False): self.num_rw_ports = OPTS.num_rw_ports self.num_w_ports = OPTS.num_w_ports self.num_r_ports = OPTS.num_r_ports self.total_ports = self.num_rw_ports + self.num_w_ports + self.num_r_ports self.replica_bitcell = replica_bitcell + self.dummy_bitcell = dummy_bitcell design.design.__init__(self, name) - debug.info(2, "create a multi-port bitcell with {0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, - self.num_w_ports, - self.num_r_ports)) + info_string = "{0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, + self.num_w_ports, + self.num_r_ports) + debug.info(2, "create a multi-port bitcell with {}".format(info_string)) + self.add_comment(info_string) + + if self.dummy_bitcell: + self.add_comment("dummy bitcell") + if self.replica_bitcell: + self.add_comment("replica bitcell") self.create_netlist() - # We must always create the bitcell layout because some transistor sizes in the other netlists depend on it + # We must always create the bitcell layout + # because some transistor sizes in the other netlists depend on it self.create_layout() self.add_boundary() @@ -376,14 +385,20 @@ class pbitcell(design.design): # iterate over the number of read/write ports for k in range(0,self.num_rw_ports): + bl_name = self.rw_bl_names[k] + br_name = self.rw_br_names[k] + if self.dummy_bitcell: + bl_name += "_noconn" + br_name += "_noconn" + # add read/write transistors self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k), mod=self.readwrite_nmos) - self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], self.Q, "gnd"]) + self.connect_inst([bl_name, self.rw_wl_names[k], self.Q, "gnd"]) self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), mod=self.readwrite_nmos) - self.connect_inst([self.Q_bar, self.rw_wl_names[k], self.rw_br_names[k], "gnd"]) + self.connect_inst([self.Q_bar, self.rw_wl_names[k], br_name, "gnd"]) def place_readwrite_ports(self): """ Places read/write ports in the bit cell """ @@ -450,14 +465,20 @@ class pbitcell(design.design): # iterate over the number of write ports for k in range(0,self.num_w_ports): + bl_name = self.w_bl_names[k] + br_name = self.w_br_names[k] + if self.dummy_bitcell: + bl_name += "_noconn" + br_name += "_noconn" + # add write transistors self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), mod=self.write_nmos) - self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], self.Q, "gnd"]) + self.connect_inst([bl_name, self.w_wl_names[k], self.Q, "gnd"]) self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), mod=self.write_nmos) - self.connect_inst([self.Q_bar, self.w_wl_names[k], self.w_br_names[k], "gnd"]) + self.connect_inst([self.Q_bar, self.w_wl_names[k], br_name, "gnd"]) def place_write_ports(self): """ Places write ports in the bit cell """ @@ -532,6 +553,12 @@ class pbitcell(design.design): # iterate over the number of read ports for k in range(0,self.num_r_ports): + bl_name = self.r_bl_names[k] + br_name = self.r_br_names[k] + if self.dummy_bitcell: + bl_name += "_noconn" + br_name += "_noconn" + # add read-access transistors self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k), mod=self.read_nmos) @@ -544,11 +571,11 @@ class pbitcell(design.design): # add read transistors self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), mod=self.read_nmos) - self.connect_inst([self.r_bl_names[k], self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"]) + self.connect_inst([bl_name, self.r_wl_names[k], "RA_to_R_left{}".format(k), "gnd"]) self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k), mod=self.read_nmos) - self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], self.r_br_names[k], "gnd"]) + self.connect_inst(["RA_to_R_right{}".format(k), self.r_wl_names[k], br_name, "gnd"]) def place_read_ports(self): """ Places the read ports in the bit cell """ @@ -686,8 +713,10 @@ class pbitcell(design.design): port_contact_offest = left_port_transistors[k].get_pin("S").center() bl_offset = vector(bl_positions[k].x, port_contact_offest.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=port_contact_offest) + # Leave bitline disconnected if a dummy cell + if not self.dummy_bitcell: + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=port_contact_offest) self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height) @@ -695,8 +724,10 @@ class pbitcell(design.design): port_contact_offest = right_port_transistors[k].get_pin("D").center() br_offset = vector(br_positions[k].x, port_contact_offest.y) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=port_contact_offest) + # Leave bitline disconnected if a dummy cell + if not self.dummy_bitcell: + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=port_contact_offest) self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height) @@ -846,7 +877,7 @@ class pbitcell(design.design): implant_type="n", well_type="n") - def list_bitcell_pins(self, col, row): + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = [] for port in range(self.total_ports): @@ -858,12 +889,12 @@ class pbitcell(design.design): bitcell_pins.append("gnd") return bitcell_pins - def list_all_wl_names(self): + def get_all_wl_names(self): """ Creates a list of all wordline pin names """ wordline_names = self.rw_wl_names + self.w_wl_names + self.r_wl_names return wordline_names - def list_all_bitline_names(self): + def get_all_bitline_names(self): """ Creates a list of all bitline pin names (both bl and br) """ bitline_pins = [] for port in range(self.total_ports): @@ -871,15 +902,13 @@ class pbitcell(design.design): bitline_pins.append("br{0}".format(port)) return bitline_pins - def list_all_bl_names(self): + def get_all_bl_names(self): """ Creates a list of all bl pins names """ - bl_pins = self.rw_bl_names + self.w_bl_names + self.r_bl_names - return bl_pins + return self.rw_bl_names + self.w_bl_names + self.r_bl_names - def list_all_br_names(self): + def get_all_br_names(self): """ Creates a list of all br pins names """ - br_pins = self.rw_br_names + self.w_br_names + self.r_br_names - return br_pins + return self.rw_br_names + self.w_br_names + self.r_br_names def route_rbc_short(self): """ route the short from Q_bar to gnd necessary for the replica bitcell """ @@ -897,7 +926,13 @@ class pbitcell(design.design): def get_br_name(self, port=0): """Get bl name by port""" - return "br{}".format(port) + return "br{}".format(port) + + def get_wl_name(self, port=0): + """Get wl name by port""" + debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + return "wl{}".format(port) + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -927,11 +962,16 @@ class pbitcell(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph for pbitcell. Only readwrite and read ports.""" + + if self.dummy_bitcell: + return + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} # Edges added wl->bl, wl->br for every port except write ports rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names) r_pin_names = zip(self.rw_wl_names, self.rw_bl_names, self.rw_br_names) - for pin_zip in zip(rw_pin_names, r_pin_names): + + for pin_zip in [rw_pin_names, r_pin_names]: for wl,bl,br in pin_zip: graph.add_edge(pin_dict[wl],pin_dict[bl]) graph.add_edge(pin_dict[wl],pin_dict[br]) diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index c0d31a69..79171bf5 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -43,9 +43,10 @@ class replica_bitcell_1w_1r(design.design): def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" + debug.info(1,'Adding edges for {}'.format(inst_name)) pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} - #Edges hardcoded here. Essentially wl->bl/br for both ports. - # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) - # Port 1 is a write port, so its timing is not considered here. \ No newline at end of file + #Edges hardcoded here. Essentially wl->bl/br for the read port. + # Port 1 edges + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) + # Port 0 is a write port, so its timing is not considered here. \ No newline at end of file diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 8153251b..93dd5bcb 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -13,7 +13,6 @@ from .lib import * from .delay import * from .setup_hold import * from .functional import * -from .worst_case import * from .simulation import * from .measurements import * from .model_check import * diff --git a/compiler/characterizer/bit_polarity.py b/compiler/characterizer/bit_polarity.py new file mode 100644 index 00000000..c14c167e --- /dev/null +++ b/compiler/characterizer/bit_polarity.py @@ -0,0 +1,14 @@ +# 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. +# + +from enum import Enum + +class bit_polarity(Enum): + NONINVERTING = 0 + INVERTING = 1 + diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 6cc995e1..fa49b1ed 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -8,17 +8,7 @@ import re import debug from globals import OPTS -from enum import Enum -class sram_op(Enum): - READ_ZERO = 0 - READ_ONE = 1 - WRITE_ZERO = 2 - WRITE_ONE = 3 - -class bit_polarity(Enum): - NONINVERTING = 0 - INVERTING = 1 def relative_compare(value1,value2,error_tolerance=0.001): """ This is used to compare relative values for convergence. """ @@ -102,4 +92,4 @@ def check_dict_values_is_float(dict): for key, value in dict.items(): if type(value)!=float: return False - return True \ No newline at end of file + return True diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 41ab7993..bb48564e 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -12,6 +12,8 @@ import math from .stimuli import * from .trim_spice import * from .charutils import * +from .sram_op import * +from .bit_polarity import * import utils from globals import OPTS from .simulation import simulation @@ -21,7 +23,8 @@ import graph_util from sram_factory import factory class delay(simulation): - """Functions to measure the delay and power of an SRAM at a given address and + """ + Functions to measure the delay and power of an SRAM at a given address and data bit. In general, this will perform the following actions: @@ -40,7 +43,6 @@ class delay(simulation): def __init__(self, sram, spfile, corner): simulation.__init__(self, sram, spfile, corner) - # These are the member variables for a simulation self.targ_read_ports = [] self.targ_write_ports = [] self.period = 0 @@ -54,39 +56,42 @@ class delay(simulation): self.add_graph_exclusions() def create_measurement_names(self): - """Create measurement names. The names themselves currently define the type of measurement""" - #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. + """ Create measurement names. The names themselves currently define the type of measurement """ + self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"] self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] - #self.voltage_when_names = ["volt_bl", "volt_br"] - #self.bitline_delay_names = ["delay_bl", "delay_br"] + # self.voltage_when_names = ["volt_bl", "volt_br"] + # self.bitline_delay_names = ["delay_bl", "delay_br"] def create_measurement_objects(self): - """Create the measurements used for read and write ports""" + """ Create the measurements used for read and write ports """ + self.read_meas_lists = self.create_read_port_measurement_objects() self.write_meas_lists = self.create_write_port_measurement_objects() self.check_meas_names(self.read_meas_lists+self.write_meas_lists) def check_meas_names(self, measures_lists): - """Given measurements (in 2d list), checks that their names are unique. - Spice sim will fail otherwise.""" + """ + Given measurements (in 2d list), checks that their names are unique. + Spice sim will fail otherwise. + """ name_set = set() for meas_list in measures_lists: for meas in meas_list: name = meas.name.lower() debug.check(name not in name_set,("SPICE measurements must have unique names. " - "Duplicate name={}").format(name)) + "Duplicate name={}").format(name)) name_set.add(name) def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" - + self.read_lib_meas = [] - self.clk_frmt = "clk{0}" #Unformatted clock name - targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit + self.clk_frmt = "clk{0}" # Unformatted clock name + targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) # Empty values are the port and probe data bit self.delay_meas = [] self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) - self.delay_meas[-1].meta_str = sram_op.READ_ONE #Used to index time delay values when measurements written to spice file. + self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file. self.delay_meas.append(delay_measure("delay_hl", self.clk_frmt, targ_name, "FALL", "FALL", measure_scale=1e9)) self.delay_meas[-1].meta_str = sram_op.READ_ZERO self.read_lib_meas+=self.delay_meas @@ -103,14 +108,14 @@ class delay(simulation): self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3)) self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO - #This will later add a half-period to the spice time delay. Only for reading 0. + # This will later add a half-period to the spice time delay. Only for reading 0. for obj in self.read_lib_meas: if obj.meta_str is sram_op.READ_ZERO: obj.meta_add_delay = True read_measures = [] read_measures.append(self.read_lib_meas) - #Other measurements associated with the read port not included in the liberty file + # Other measurements associated with the read port not included in the liberty file read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_measurement_objects()) read_measures.append(self.create_read_bit_measures()) @@ -118,11 +123,14 @@ class delay(simulation): return read_measures def create_bitline_measurement_objects(self): - """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements. - These measurements are only associated with read values """ + Create the measurements used for bitline delay values. Due to + unique error checking, these are separated from other measurements. + These measurements are only associated with read values. + """ + self.bitline_volt_meas = [] - #Bitline voltage measures + self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ZERO", self.bl_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO @@ -131,15 +139,16 @@ class delay(simulation): self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ONE", - self.bl_name)) + self.bl_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ONE", - self.br_name)) + self.br_name)) self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE return self.bitline_volt_meas def create_write_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" + self.write_lib_meas = [] self.write_lib_meas.append(power_measure("write1_power", "RISE", measure_scale=1e3)) @@ -149,25 +158,28 @@ class delay(simulation): write_measures = [] write_measures.append(self.write_lib_meas) + write_measures.append(self.create_write_bit_measures()) return write_measures def create_debug_measurement_objects(self): """Create debug measurement to help identify failures.""" - self.debug_volt_meas = [] + + self.dout_volt_meas = [] for meas in self.delay_meas: - #Output voltage measures - self.debug_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), + # Output voltage measures + self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), meas.targ_name_no_port)) - self.debug_volt_meas[-1].meta_str = meas.meta_str + self.dout_volt_meas[-1].meta_str = meas.meta_str self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9) self.sen_meas.meta_str = sram_op.READ_ZERO self.sen_meas.meta_add_delay = True - return self.debug_volt_meas+[self.sen_meas] + return self.dout_volt_meas+[self.sen_meas] def create_read_bit_measures(self): - """Adds bit measurements for read0 and read1 cycles""" + """ Adds bit measurements for read0 and read1 cycles """ + self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) for cycle in meas_cycles: @@ -176,21 +188,39 @@ class delay(simulation): for polarity,meas in single_bit_meas.items(): meas.meta_str = cycle self.bit_meas[polarity].append(meas) - #Dictionary values are lists, reduce to a single list of measurements + # Dictionary values are lists, reduce to a single list of measurements return [meas for meas_list in self.bit_meas.values() for meas in meas_list] - + + def create_write_bit_measures(self): + """ Adds bit measurements for write0 and write1 cycles """ + + self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]} + meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) + for cycle in meas_cycles: + meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) + single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) + for polarity,meas in single_bit_meas.items(): + meas.meta_str = cycle + self.bit_meas[polarity].append(meas) + # Dictionary values are lists, reduce to a single list of measurements + return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + def get_bit_measures(self, meas_tag, probe_address, probe_data): - """Creates measurements for the q/qbar of input bit position. - meas_tag is a unique identifier for the measurement.""" + """ + Creates measurements for the q/qbar of input bit position. + meas_tag is a unique identifier for the measurement. + """ + bit_col = self.get_data_bit_column_number(probe_address, probe_data) bit_row = self.get_address_row_number(probe_address) (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col) storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" - "supported for characterization. Storage nets={}").format(storage_names)) + "supported for characterization. Storage nets={}").format(storage_names)) q_name = cell_name+'.'+str(storage_names[0]) qbar_name = cell_name+'.'+str(storage_names[1]) - #Bit measures, measurements times to be defined later. The measurement names must be unique + + # Bit measures, measurements times to be defined later. The measurement names must be unique # but they is enforced externally q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False) qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False) @@ -199,80 +229,88 @@ class delay(simulation): def set_load_slew(self,load,slew): """ Set the load and slew """ + self.load = load self.slew = slew def add_graph_exclusions(self): """Exclude portions of SRAM from timing graph which are not relevant""" - #other initializations can only be done during analysis when a bit has been selected - #for testing. + + # other initializations can only be done during analysis when a bit has been selected + # for testing. self.sram.bank.graph_exclude_precharge() self.sram.graph_exclude_addr_dff() self.sram.graph_exclude_data_dff() self.sram.graph_exclude_ctrl_dffs() + self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() def create_graph(self): """Creates timing graph to generate the timing paths for the SRAM output.""" - self.sram.bank.bitcell_array.init_graph_params() #Removes previous bit exclusions + + self.sram.bank.bitcell_array.init_graph_params() # Removes previous bit exclusions self.sram.bank.bitcell_array.graph_exclude_bits(self.wordline_row, self.bitline_column) - #Generate new graph every analysis as edges might change depending on test bit + # Generate new graph every analysis as edges might change depending on test bit self.graph = graph_util.timing_graph() self.sram_spc_name = "X{}".format(self.sram.name) self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) def set_internal_spice_names(self): """Sets important names for characterization such as Sense amp enable and internal bit nets.""" - port = 0 - self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ - '{}{}_{}'.format(self.dout_name, port, self.probe_data)) - + + port = self.read_ports[0] + self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + self.sen_name = self.get_sen_name(self.graph.all_paths) debug.info(2,"s_en name = {}".format(self.sen_name)) - self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths) + self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) - + def get_sen_name(self, paths): - """Gets the signal name associated with the sense amp enable from input paths. - Only expects a single path to contain the sen signal name.""" + """ + Gets the signal name associated with the sense amp enable from input paths. + Only expects a single path to contain the sen signal name. + """ + sa_mods = factory.get_mods(OPTS.sense_amp) - #Any sense amp instantiated should be identical, any change to that - #will require some identification to determine the mod desired. + # Any sense amp instantiated should be identical, any change to that + # will require some identification to determine the mod desired. debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") enable_name = sa_mods[0].get_enable_name() sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) return sen_name - def get_bl_name(self, paths): + def get_bl_name(self, paths, port): """Gets the signal name associated with the bitlines in the bank.""" - cell_mods = factory.get_mods(OPTS.bitcell) - if len(cell_mods)>=1: - cell_mod = self.get_primary_cell_mod(cell_mods) - elif len(cell_mods)==0: - debug.error("No bitcells found. Cannot determine bitline names.", 1) - - cell_bl = cell_mod.get_bl_name() - cell_br = cell_mod.get_br_name() + + cell_mod = factory.create(module_type=OPTS.bitcell) + cell_bl = cell_mod.get_bl_name(port) + cell_br = cell_mod.get_br_name(port) bl_found = False - #Only a single path should contain a single s_en name. Anything else is an error. + # Only a single path should contain a single s_en name. Anything else is an error. bl_names = [] exclude_set = self.get_bl_name_search_exclusions() for int_net in [cell_bl, cell_br]: bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - return bl_names[0], bl_names[1] - + return bl_names[0], bl_names[1] + + def get_bl_name_search_exclusions(self): """Gets the mods as a set which should be excluded while searching for name.""" - #Exclude the RBL as it contains bitcells which are not in the main bitcell array - #so it makes the search awkward + + # Exclude the RBL as it contains bitcells which are not in the main bitcell array + # so it makes the search awkward return set(factory.get_mods(OPTS.replica_bitline)) def get_primary_cell_mod(self, cell_mods): - """Distinguish bitcell array mod from replica bitline array. - Assume there are no replica bitcells in the primary array.""" + """ + Distinguish bitcell array mod from replica bitline array. + Assume there are no replica bitcells in the primary array. + """ if len(cell_mods) == 1: return cell_mods[0] rbc_mods = factory.get_mods(OPTS.replica_bitcell) @@ -289,6 +327,7 @@ class delay(simulation): def are_mod_pins_equal(self, mods): """Determines if there are pins differences in the input mods""" + if len(mods) == 0: return True pins = mods[0].pins @@ -298,8 +337,11 @@ class delay(simulation): return True def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): - """Finds a single alias for the int_net in given paths. - More or less hits cause an error""" + """ + Finds a single alias for the int_net in given paths. + More or less hits cause an error + """ + net_found = False for path in paths: aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) @@ -317,6 +359,7 @@ class delay(simulation): def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" + try: int(self.probe_address, 2) except ValueError: @@ -328,7 +371,7 @@ class delay(simulation): if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0: debug.error("Given probe_data is not an integer to specify a data bit",1) - #Adding port options here which the characterizer cannot handle. Some may be added later like ROM + # Adding port options here which the characterizer cannot handle. Some may be added later like ROM if len(self.read_ports) == 0: debug.error("Characterizer does not currently support SRAMs without read ports.",1) if len(self.write_ports) == 0: @@ -352,10 +395,12 @@ class delay(simulation): def write_delay_stimulus(self): - """ Creates a stimulus file for simulations to probe a bitcell at a given clock period. + """ + Creates a stimulus file for simulations to probe a bitcell at a given clock period. Address and bit were previously set with set_probe(). Input slew (in ns) and output capacitive load (in fF) are required for charaterization. """ + self.check_arguments() # obtains list of time-points for each rising clk edge @@ -450,39 +495,47 @@ class delay(simulation): self.sf.close() - def get_read_measure_variants(self, port, measure_obj): - """Checks the measurement object and calls respective function for related measurement inputs.""" + def get_measure_variants(self, port, measure_obj, measure_type=None): + """ + Checks the measurement object and calls respective function for + related measurement inputs. + """ + meas_type = type(measure_obj) if meas_type is delay_measure or meas_type is slew_measure: variant_tuple = self.get_delay_measure_variants(port, measure_obj) elif meas_type is power_measure: - variant_tuple = self.get_power_measure_variants(port, measure_obj, "read") + variant_tuple = self.get_power_measure_variants(port, measure_obj, measure_type) elif meas_type is voltage_when_measure: variant_tuple = self.get_volt_when_measure_variants(port, measure_obj) elif meas_type is voltage_at_measure: variant_tuple = self.get_volt_at_measure_variants(port, measure_obj) else: debug.error("Input function not defined for measurement type={}".format(meas_type)) - #Removes port input from any object which does not use it. This shorthand only works if - #the measurement has port as the last input. Could be implemented by measurement type or - #remove entirely from measurement classes. + # Removes port input from any object which does not use it. This shorthand only works if + # the measurement has port as the last input. Could be implemented by measurement type or + # remove entirely from measurement classes. if not measure_obj.has_port: variant_tuple = variant_tuple[:-1] return variant_tuple def get_delay_measure_variants(self, port, delay_obj): - """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)""" - #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port - #vdd is arguably constant as that is true for a single lib file. + """ + Get the measurement values that can either vary from simulation to + simulation (vdd, address) or port to port (time delays) + """ + + # Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port + # vdd is arguably constant as that is true for a single lib file. if delay_obj.meta_str == sram_op.READ_ZERO: - #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. + # Falling delay are measured starting from neg. clk edge. Delay adjusted to that. meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] elif delay_obj.meta_str == sram_op.READ_ONE: meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] else: debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1) - #These measurements have there time further delayed to the neg. edge of the clock. + # These measurements have there time further delayed to the neg. edge of the clock. if delay_obj.meta_add_delay: meas_cycle_delay += self.period/2 @@ -490,29 +543,32 @@ class delay(simulation): def get_power_measure_variants(self, port, power_obj, operation): """Get the measurement values that can either vary port to port (time delays)""" - #Return value is intended to match the power measure format: t_initial, t_final, port + + # Return value is intended to match the power measure format: t_initial, t_final, port t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]] t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1] return (t_initial, t_final, port) def get_volt_at_measure_variants(self, port, volt_meas): - """Get the measurement values that can either vary port to port (time delays)""" - #Only checking 0 value reads for now. - if volt_meas.meta_str == sram_op.READ_ZERO: - #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. - meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] - elif volt_meas.meta_str == sram_op.READ_ONE: - meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] - else: - debug.error("Unrecognised delay Index={}".format(volt_meas.meta_str),1) - #Measurement occurs at the end of the period -> current period start + period - at_time = meas_cycle+self.period + """ + Get the measurement values that can either vary port to port (time delays) + """ + + meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]] + + # Measurement occurs slightly into the next period so we know that the value + # "stuck" after the end of the period -> current period start + 1.25*period + at_time = meas_cycle+1.25*self.period + return (at_time, port) def get_volt_when_measure_variants(self, port, volt_meas): - """Get the measurement values that can either vary port to port (time delays)""" - #Only checking 0 value reads for now. + """ + Get the measurement values that can either vary port to port (time delays) + """ + + # Only checking 0 value reads for now. t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]] return (t_trig, self.vdd_voltage, port) @@ -521,45 +577,45 @@ class delay(simulation): """ Write the measure statements to quantify the delay and power results for a read port. """ + # add measure statements for delays/slews for meas_list in self.read_meas_lists: for measure in meas_list: - measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure_variant_inp_tuple = self.get_measure_variants(port, measure, "read") measure.write_measure(self.stim, measure_variant_inp_tuple) - def get_write_measure_variants(self, port, measure_obj): - """Checks the measurement object and calls respective function for related measurement inputs.""" - meas_type = type(measure_obj) - if meas_type is power_measure: - return self.get_power_measure_variants(port, measure_obj, "write") - else: - debug.error("Input function not defined for measurement type={}".format(meas_type)) def write_delay_measures_write_port(self, port): """ Write the measure statements to quantify the power results for a write port. """ + # add measure statements for power for meas_list in self.write_meas_lists: for measure in meas_list: - measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) + measure_variant_inp_tuple = self.get_measure_variants(port, measure, "write") measure.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): """ Write the measure statements to quantify the delay and power results for all targeted ports. """ + self.sf.write("\n* Measure statements for delay and power\n") # Output some comments to aid where cycles start and # what is happening for comment in self.cycle_comments: self.sf.write("* {}\n".format(comment)) - + + self.sf.write("\n") for read_port in self.targ_read_ports: - self.write_delay_measures_read_port(read_port) + self.sf.write("* Read ports {}\n".format(read_port)) + self.write_delay_measures_read_port(read_port) + for write_port in self.targ_write_ports: - self.write_delay_measures_write_port(write_port) + self.sf.write("* Write ports {}\n".format(write_port)) + self.write_delay_measures_write_port(write_port) def write_power_measures(self): @@ -593,35 +649,36 @@ class delay(simulation): if (time_out <= 0): debug.error("Timed out, could not find a feasible period.",2) - #Clear any write target ports and set read port - self.targ_write_ports = [] + # Clear any write target ports and set read port + self.targ_write_ports = [port] self.targ_read_ports = [port] - success = False debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port)) self.period = feasible_period (success, results)=self.run_delay_simulation() - #Clear these target ports after simulation + + # Clear these target ports after simulation + self.targ_write_ports = [] self.targ_read_ports = [] if not success: feasible_period = 2 * feasible_period continue - #Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews + # Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname] feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname] delay_str = "feasible_delay {0:.4f}ns/{1:.4f}ns".format(*feasible_delays) slew_str = "slew {0:.4f}ns/{1:.4f}ns".format(*feasible_slews) debug.info(2, "feasible_period passed for Port {3}: {0}ns {1} {2} ".format(feasible_period, - delay_str, - slew_str, - port)) + delay_str, + slew_str, + port)) if success: debug.info(2, "Found feasible_period for port {0}: {1}ns".format(port, feasible_period)) self.period = feasible_period - #Only return results related to input port. + # Only return results related to input port. return results[port] def find_feasible_period(self): @@ -631,19 +688,19 @@ class delay(simulation): """ feasible_delays = [{} for i in self.all_ports] - #Get initial feasible delays from first port + # Get initial feasible delays from first port feasible_delays[self.read_ports[0]] = self.find_feasible_period_one_port(self.read_ports[0]) previous_period = self.period - #Loops through all the ports checks if the feasible period works. Everything restarts it if does not. - #Write ports do not produce delays which is why they are not included here. + # Loops through all the ports checks if the feasible period works. Everything restarts it if does not. + # Write ports do not produce delays which is why they are not included here. i = 1 while i < len(self.read_ports): port = self.read_ports[i] - #Only extract port values from the specified port, not the entire results. + # Only extract port values from the specified port, not the entire results. feasible_delays[port].update(self.find_feasible_period_one_port(port)) - #Function sets the period. Restart the entire process if period changes to collect accurate delays + # Function sets the period. Restart the entire process if period changes to collect accurate delays if self.period > previous_period: i = 0 else: @@ -659,66 +716,79 @@ class delay(simulation): works on the trimmed netlist by default, so powers do not include leakage of all cells. """ - #Sanity Check + debug.check(self.period > 0, "Target simulation period non-positive") - sim_passed = True - result = [{} for i in self.all_ports] - # Checking from not data_value to data_value self.write_delay_stimulus() self.stim.run_sim() + + return self.check_measurements() + + def check_measurements(self): + """ Check the write and read measurements """ - #Loop through all targeted ports and collect delays and powers. - #Too much duplicate code here. Try reducing - for port in self.targ_read_ports: - debug.info(2, "Checking delay values for port {}".format(port)) - read_port_dict = {} - #Get measurements from output file - for measure in self.read_lib_meas: - read_port_dict[measure.name] = measure.retrieve_measure(port=port) - - #Check sen timing, then bitlines, then general measurements. - if not self.check_sen_measure(port): - return (False,{}) - success = self.check_debug_measures(port) - success = success and self.check_bit_measures() - #Check timing for read ports. Power is only checked if it was read correctly - if not self.check_valid_delays(read_port_dict) or not success: - return (False,{}) - if not check_dict_values_is_float(read_port_dict): - debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad. - - result[port].update(read_port_dict) - + # Loop through all targeted ports and collect delays and powers. + result = [{} for i in self.all_ports] + + + # First, check that the memory has the right values at the right times + if not self.check_bit_measures(): + return(False,{}) + for port in self.targ_write_ports: + debug.info(2, "Checking write values for port {}".format(port)) write_port_dict = {} for measure in self.write_lib_meas: write_port_dict[measure.name] = measure.retrieve_measure(port=port) if not check_dict_values_is_float(write_port_dict): - debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) #Printing the entire dict looks bad. + debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) result[port].update(write_port_dict) - # The delay is from the negative edge for our SRAM - return (sim_passed,result) + + for port in self.targ_read_ports: + debug.info(2, "Checking read delay values for port {}".format(port)) + # Check sen timing, then bitlines, then general measurements. + if not self.check_sen_measure(port): + return (False,{}) + + if not self.check_read_debug_measures(port): + return (False,{}) + + # Check timing for read ports. Power is only checked if it was read correctly + read_port_dict = {} + for measure in self.read_lib_meas: + read_port_dict[measure.name] = measure.retrieve_measure(port=port) + + if not self.check_valid_delays(read_port_dict): + return (False,{}) + + if not check_dict_values_is_float(read_port_dict): + debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) + + result[port].update(read_port_dict) + + return (True,result) def check_sen_measure(self, port): """Checks that the sen occurred within a half-period""" + sen_val = self.sen_meas.retrieve_measure(port=port) - debug.info(2,"S_EN delay={} ns".format(sen_val)) + debug.info(2,"s_en delay={}ns".format(sen_val)) if self.sen_meas.meta_add_delay: max_delay = self.period/2 else: max_delay = self.period return not (type(sen_val) != float or sen_val > max_delay) - def check_debug_measures(self, port): + + def check_read_debug_measures(self, port): """Debug measures that indicate special conditions.""" - #Currently, only check if the opposite than intended value was read during + + # Currently, only check if the opposite than intended value was read during # the read cycles i.e. neither of these measurements should pass. - success = True - #FIXME: these checks need to be re-done to be more robust against possible errors + # FIXME: these checks need to be re-done to be more robust against possible errors bl_vals = {} br_vals = {} for meas in self.bitline_volt_meas: @@ -729,35 +799,43 @@ class delay(simulation): br_vals[meas.meta_str] = val debug.info(2,"{}={}".format(meas.name,val)) - - bl_check = False - for meas in self.debug_volt_meas: + + for meas in self.dout_volt_meas: val = meas.retrieve_measure(port=port) debug.info(2,"{}={}".format(meas.name, val)) - if type(val) != float: - continue + debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name,val)) - if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage*.1: - success = False - debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val)) - bl_check = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE]) - elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage*.9: - success = False - debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) - bl_check = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) - - #If the bitlines have a correct value while the output does not then that is a - #sen error. FIXME: there are other checks that can be done to solidfy this conclusion. - if bl_check: + if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage*0.1: + dout_success = False + debug.info(1, "Debug measurement failed. Value {}V was read on read 1 cycle.".format(val)) + bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE]) + elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage*0.9: + dout_success = False + debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val)) + bl_success = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) + elif meas.meta_str == sram_op.READ_ONE and val > self.vdd_voltage*0.9: + dout_success = True + bl_success = True + elif meas.meta_str == sram_op.READ_ZERO and val < self.vdd_voltage*0.1: + dout_success = True + bl_success = True + else: + dout_success = False + bl_success = False + # If the bitlines have a correct value while the output does not then that is a + # sen error. FIXME: there are other checks that can be done to solidfy this conclusion. + if not dout_success and bl_success: debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) - return success + return dout_success def check_bit_measures(self): - """Checks the measurements which represent the internal storage voltages - at the end of the read cycle.""" - success = True + """ + Checks the measurements which represent the internal storage voltages + at the end of the read cycle. + """ + success = False for polarity, meas_list in self.bit_meas.items(): for meas in meas_list: val = meas.retrieve_measure() @@ -765,25 +843,34 @@ class delay(simulation): if type(val) != float: continue meas_cycle = meas.meta_str - #Loose error conditions. Assume it's not metastable but account for noise during reads. + # Loose error conditions. Assume it's not metastable but account for noise during reads. if (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.NONINVERTING) or\ (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.INVERTING): success = val < self.vdd_voltage/2 elif (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.INVERTING) or\ (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.NONINVERTING): success = val > self.vdd_voltage/2 + elif (meas_cycle == sram_op.WRITE_ZERO and polarity == bit_polarity.INVERTING) or\ + (meas_cycle == sram_op.WRITE_ONE and polarity == bit_polarity.NONINVERTING): + success = val > self.vdd_voltage/2 + elif (meas_cycle == sram_op.WRITE_ONE and polarity == bit_polarity.INVERTING) or\ + (meas_cycle == sram_op.WRITE_ZERO and polarity == bit_polarity.NONINVERTING): + success = val < self.vdd_voltage/2 if not success: - debug.info(1,("Wrong value detected on probe bit during read cycle. " - "Check writes and control logic for bugs.\n measure={}, op={}, " - "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name,val)) + debug.info(1,("Wrong value detected on probe bit during read/write cycle. " + "Check writes and control logic for bugs.\n measure={}, op={}, " + "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name,val)) + return success def check_bitline_meas(self, v_discharged_bl, v_charged_bl): - """Checks the value of the discharging bitline. Confirms s_en timing errors. - Returns true if the bitlines are at there expected value.""" - #The inputs looks at discharge/charged bitline rather than left or right (bl/br) - #Performs two checks, discharging bitline is at least 10% away from vdd and there is a - #10% vdd difference between the bitlines. Both need to fail to be considered a s_en error. + """ + Checks the value of the discharging bitline. Confirms s_en timing errors. + Returns true if the bitlines are at there expected value. + """ + # The inputs looks at discharge/charged bitline rather than left or right (bl/br) + # Performs two checks, discharging bitline is at least 10% away from vdd and there is a + # 10% vdd difference between the bitlines. Both need to fail to be considered a s_en error. min_dicharge = v_discharged_bl < self.vdd_voltage*0.9 min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage*0.1 @@ -793,16 +880,16 @@ class delay(simulation): def run_power_simulation(self): """ This simulates a disabled SRAM to get the leakage power when it is off. - """ + debug.info(1, "Performing leakage power simulations.") self.write_power_stimulus(trim=False) self.stim.run_sim() leakage_power=parse_spice_list("timing", "leakage_power") debug.check(leakage_power!="Failed","Could not measure leakage power.") debug.info(1, "Leakage power of full array is {0} mW".format(leakage_power*1e3)) - #debug - #sys.exit(1) + # debug + # sys.exit(1) self.write_power_stimulus(trim=True) self.stim.run_sim() @@ -811,12 +898,13 @@ class delay(simulation): debug.info(1, "Leakage power of trimmed array is {0} mW".format(trim_leakage_power*1e3)) # For debug, you sometimes want to inspect each simulation. - #key=raw_input("press return to continue") + # key=raw_input("press return to continue") return (leakage_power*1e3, trim_leakage_power*1e3) def check_valid_delays(self, result_dict): """ Check if the measurements are defined and if they are valid. """ - #Hard coded names currently + + # Hard coded names currently delay_hl = result_dict["delay_hl"] delay_lh = result_dict["delay_lh"] slew_hl = result_dict["slew_hl"] @@ -834,7 +922,7 @@ class delay(simulation): delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh) slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh) - half_period = self.period/2 #high-to-low delays start at neg. clk edge, so they need to be less than half_period + half_period = self.period/2 # high-to-low delays start at neg. clk edge, so they need to be less than half_period if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \ or delay_hl<0 or delay_lh<0 or slew_hl<0 or slew_lh<0: debug.info(2,"UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, @@ -857,15 +945,15 @@ class delay(simulation): lb_period = 0.0 target_period = 0.5 * (ub_period + lb_period) - #Find the minimum period for all ports. Start at one port and perform binary search then use that delay as a starting position. - #For testing purposes, only checks read ports. + # Find the minimum period for all ports. Start at one port and perform binary search then use that delay as a starting position. + # For testing purposes, only checks read ports. for port in self.read_ports: target_period = self.find_min_period_one_port(feasible_delays, port, lb_period, ub_period, target_period) - #The min period of one port becomes the new lower bound. Reset the upper_bound. + # The min period of one port becomes the new lower bound. Reset the upper_bound. lb_period = target_period ub_period = feasible_period - #Clear the target ports before leaving + # Clear the target ports before leaving self.targ_read_ports = [] self.targ_write_ports = [] return target_period @@ -876,13 +964,14 @@ class delay(simulation): long period. For the current logic to characterize multiport, bounds are required as an input. """ - #previous_period = ub_period = self.period - #ub_period = self.period - #lb_period = 0.0 - #target_period = 0.5 * (ub_period + lb_period) + # previous_period = ub_period = self.period + # ub_period = self.period + # lb_period = 0.0 + # target_period = 0.5 * (ub_period + lb_period) # Binary search algorithm to find the min period (max frequency) of input port time_out = 25 + self.targ_write_ports = [port] self.targ_read_ports = [port] while True: time_out -= 1 @@ -891,9 +980,9 @@ class delay(simulation): self.period = target_period debug.info(1, "MinPeriod Search Port {3}: {0}ns (ub: {1} lb: {2})".format(target_period, - ub_period, - lb_period, - port)) + ub_period, + lb_period, + port)) if self.try_period(feasible_delays): ub_period = target_period @@ -904,9 +993,9 @@ class delay(simulation): # ub_period is always feasible. return ub_period - #Update target + # Update target target_period = 0.5 * (ub_period + lb_period) - #key=input("press return to continue") + # key=input("press return to continue") def try_period(self, feasible_delays): @@ -914,18 +1003,20 @@ class delay(simulation): This tries to simulate a period and checks if the result works. If it does and the delay is within 5% still, it returns True. """ + # Run Delay simulation but Power results not used. (success, results) = self.run_delay_simulation() if not success: return False - #Check the values of target readwrite and read ports. Write ports do not produce delays in this current version - for port in self.targ_read_ports: - for dname in self.delay_meas_names: #check that the delays and slews do not degrade with tested period. + # Check the values of target readwrite and read ports. Write ports do not produce delays in this current version + for port in self.targ_read_ports: + # check that the delays and slews do not degrade with tested period. + for dname in self.delay_meas_names: - #FIXME: This is a hack solution to fix the min period search. The slew will always be based on the period when there - #is a column mux. Therefore, the checks are skipped for this condition. This is hard to solve without changing the netlist. - #Delays/slews based on the period will cause the min_period search to come to the wrong period. + # FIXME: This is a hack solution to fix the min period search. The slew will always be based on the period when there + # is a column mux. Therefore, the checks are skipped for this condition. This is hard to solve without changing the netlist. + # Delays/slews based on the period will cause the min_period search to come to the wrong period. if self.sram.col_addr_size>0 and "slew" in dname: continue @@ -933,9 +1024,8 @@ class delay(simulation): debug.info(2,"Delay too big {0} vs {1}".format(results[port][dname],feasible_delays[port][dname])) return False - #key=raw_input("press return to continue") + # key=raw_input("press return to continue") - #Dynamic way to build string. A bit messy though. delay_str = ', '.join("{0}={1}ns".format(mname, results[port][mname]) for mname in self.delay_meas_names) debug.info(2,"Successful period {0}, Port {2}, {1}".format(self.period, delay_str, @@ -943,8 +1033,11 @@ class delay(simulation): return True def set_probe(self,probe_address, probe_data): - """ Probe address and data can be set separately to utilize other - functions in this characterizer besides analyze.""" + """ + Probe address and data can be set separately to utilize other + functions in this characterizer besides analyze. + """ + self.probe_address = probe_address self.probe_data = probe_data self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) @@ -953,6 +1046,7 @@ class delay(simulation): def get_data_bit_column_number(self, probe_address, probe_data): """Calculates bitline column number of data bit under test using bit position and mux size""" + if self.sram.col_addr_size>0: col_address = int(probe_address[0:self.sram.col_addr_size],2) else: @@ -962,6 +1056,7 @@ class delay(simulation): def get_address_row_number(self, probe_address): """Calculates wordline row number of data bit under test using address and column mux size""" + return int(probe_address[self.sram.col_addr_size:],2) def prepare_netlist(self): @@ -987,6 +1082,7 @@ class delay(simulation): def analysis_init(self, probe_address, probe_data): """Sets values which are dependent on the data address/bit being tested.""" + self.set_probe(probe_address, probe_data) self.create_graph() self.set_internal_spice_names() @@ -997,7 +1093,8 @@ class delay(simulation): """ Main function to characterize an SRAM for a table. Computes both delay and power characterization. """ - #Dict to hold all characterization values + + # Dict to hold all characterization values char_sram_data = {} self.analysis_init(probe_address, probe_data) @@ -1022,22 +1119,24 @@ class delay(simulation): self.period = min_period char_port_data = self.simulate_loads_and_slews(slews, loads, leakage_offset) - #FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate. + # FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate. self.alter_lh_char_data(char_port_data) return (char_sram_data, char_port_data) def alter_lh_char_data(self, char_port_data): """Copies high-to-low data to low-to-high data to make them consistent on the same clock edge.""" - #This is basically a hack solution which should be removed/fixed later. + + # This is basically a hack solution which should be removed/fixed later. for port in self.all_ports: char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl'] char_port_data[port]['slew_lh'] = char_port_data[port]['slew_hl'] def simulate_loads_and_slews(self, slews, loads, leakage_offset): """Simulate all specified output loads and input slews pairs of all ports""" + measure_data = self.get_empty_measure_data_dict() - #Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways. + # Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways. self.targ_read_ports = self.read_ports self.targ_write_ports = self.write_ports for slew in slews: @@ -1047,7 +1146,7 @@ class delay(simulation): (success, delay_results) = self.run_delay_simulation() debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(self.slew,self.load)) debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew,self.load)) - #The results has a dict for every port but dicts can be empty (e.g. ports were not targeted). + # The results has a dict for every port but dicts can be empty (e.g. ports were not targeted). for port in self.all_ports: for mname,value in delay_results[port].items(): if "power" in mname: @@ -1059,11 +1158,12 @@ class delay(simulation): def calculate_inverse_address(self): """Determine dummy test address based on probe address and column mux size.""" - #The inverse address needs to share the same bitlines as the probe address as the trimming will remove all other bitlines - #This is only an issue when there is a column mux and the address maps to different bitlines. - column_addr = self.probe_address[:self.sram.col_addr_size] #do not invert this part + + # The inverse address needs to share the same bitlines as the probe address as the trimming will remove all other bitlines + # This is only an issue when there is a column mux and the address maps to different bitlines. + column_addr = self.probe_address[:self.sram.col_addr_size] # do not invert this part inverse_address = "" - for c in self.probe_address[self.sram.col_addr_size:]: #invert everything else + for c in self.probe_address[self.sram.col_addr_size:]: # invert everything else if c=="0": inverse_address += "1" elif c=="1": @@ -1126,6 +1226,7 @@ class delay(simulation): self.probe_address,data_zeros,wmask_zeroes) def get_available_port(self,get_read_port): + """Returns the first accessible read or write port. """ if get_read_port and len(self.read_ports) > 0: return self.read_ports[0] @@ -1138,29 +1239,32 @@ class delay(simulation): self.measure_cycles = [{} for port in self.all_ports] def create_test_cycles(self): - """Returns a list of key time-points [ns] of the waveform (each rising edge) + """ + Returns a list of key time-points [ns] of the waveform (each rising edge) of the cycles to do a timing evaluation. The last time is the end of the simulation - and does not need a rising edge.""" - #Using this requires setting at least one port to target for simulation. + and does not need a rising edge. + """ + + # Using this requires setting at least one port to target for simulation. if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0: debug.error("No port selected for characterization.",1) self.set_stimulus_variables() - #Get any available read/write port in case only a single write or read ports is being characterized. + # Get any available read/write port in case only a single write or read ports is being characterized. cur_read_port = self.get_available_port(get_read_port=True) cur_write_port = self.get_available_port(get_read_port=False) debug.check(cur_read_port != None, "Characterizer requires at least 1 read port") debug.check(cur_write_port != None, "Characterizer requires at least 1 write port") - #Create test cycles for specified target ports. + # Create test cycles for specified target ports. write_pos = 0 read_pos = 0 while True: - #Exit when all ports have been characterized + # Exit when all ports have been characterized if write_pos >= len(self.targ_write_ports) and read_pos >= len(self.targ_read_ports): break - #Select new write and/or read ports for the next cycle. Use previous port if none remaining. + # Select new write and/or read ports for the next cycle. Use previous port if none remaining. if write_pos < len(self.targ_write_ports): cur_write_port = self.targ_write_ports[write_pos] write_pos+=1 @@ -1168,11 +1272,12 @@ class delay(simulation): cur_read_port = self.targ_read_ports[read_pos] read_pos+=1 - #Add test cycle of read/write port pair. One port could have been used already, but the other has not. + # Add test cycle of read/write port pair. One port could have been used already, but the other has not. self.gen_test_cycles_one_port(cur_read_port, cur_write_port) def analytical_delay(self, slews, loads): - """ Return the analytical model results for the SRAM. + """ + Return the analytical model results for the SRAM. """ if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0: debug.warning("Analytical characterization results are not supported for multiport.") @@ -1205,10 +1310,11 @@ class delay(simulation): def analytical_power(self, slews, loads): """Get the dynamic and leakage power from the SRAM""" - #slews unused, only last load is used + + # slews unused, only last load is used load = loads[-1] power = self.sram.analytical_power(self.corner, load) - #convert from nW to mW + # convert from nW to mW power.dynamic /= 1e6 power.leakage /= 1e6 debug.info(1,"Dynamic Power: {0} mW".format(power.dynamic)) @@ -1217,6 +1323,7 @@ class delay(simulation): def gen_data(self): """ Generates the PWL data inputs for a simulation timing test. """ + for write_port in self.write_ports: for i in range(self.word_size): sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i) @@ -1227,6 +1334,7 @@ class delay(simulation): Generates the address inputs for a simulation timing test. This alternates between all 1's and all 0's for the address. """ + for port in self.all_ports: for i in range(self.addr_size): sig_name = "{0}{1}_{2}".format(self.addr_name,port,i) @@ -1234,6 +1342,7 @@ class delay(simulation): def gen_control(self): """ Generates the control signals """ + for port in self.all_ports: self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) if port in self.readwrite_ports: @@ -1242,7 +1351,8 @@ class delay(simulation): def get_empty_measure_data_dict(self): """Make a dict of lists for each type of delay and power measurement to append results to""" + measure_names = self.delay_meas_names + self.power_meas_names - #Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. + # Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports] return measure_data diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index f675bab5..6dcb54e0 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -6,6 +6,7 @@ # All rights reserved. # import sys,re,shutil +import collections from design import design import debug import math @@ -15,9 +16,10 @@ from .stimuli import * from .charutils import * import utils from globals import OPTS - from .simulation import simulation from .delay import delay +import graph_util +from sram_factory import factory class functional(simulation): """ @@ -39,17 +41,24 @@ class functional(simulation): self.set_corner(corner) self.set_spice_constants() - #self.set_feasible_period(sram, spfile, corner) self.set_stimulus_variables() + + # For the debug signal names self.create_signal_names() + self.add_graph_exclusions() + self.create_graph() + self.set_internal_spice_names() + self.initialize_wmask() # Number of checks can be changed - self.num_cycles = 2 - self.stored_words = {} + self.num_cycles = 15 + # This is to have ordered keys for random selection + self.stored_words = collections.OrderedDict() self.write_check = [] self.read_check = [] + def initialize_wmask(self): self.wmask = "" if self.write_size is not None: @@ -132,7 +141,6 @@ class functional(simulation): elif op == "write": addr = self.gen_addr() word = self.gen_data() - # print("write",self.t_current,addr,word) # two ports cannot write to the same address if addr in w_addrs: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) @@ -154,7 +162,6 @@ class functional(simulation): lower = bit * self.write_size upper = lower + self.write_size - 1 new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] - # print("partial_w",self.t_current,addr,wmask,word, "partial_w_word:", new_word) # two ports cannot write to the same address if addr in w_addrs: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) @@ -165,7 +172,6 @@ class functional(simulation): w_addrs.append(addr) else: (addr,word) = random.choice(list(self.stored_words.items())) - # print("read",self.t_current,addr,word) # cannot read from an address that is currently being written to if addr in w_addrs: self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) @@ -241,23 +247,14 @@ class functional(simulation): def gen_data(self): """ Generates a random word to write. """ - rand = random.randint(0,(2**self.word_size)-1) - data_bits = self.convert_to_bin(rand,False) + random_value = random.randint(0,(2**self.word_size)-1) + data_bits = self.convert_to_bin(random_value,False) return data_bits - def gen_data_all_bits(self): - """ Generates a random word, either all 0's or all 1's, to write. """ - rand = random.randint(0,1) - bits = [] - for bit in range(self.word_size): - bits.append(rand) - data_bits = ''.join(map(str,bits)) - return data_bits - def gen_addr(self): """ Generates a random address value to write to. """ - rand = random.randint(0,(2**self.addr_size)-1) - addr_bits = self.convert_to_bin(rand,True) + random_value = random.randint(0,(2**self.addr_size)-1) + addr_bits = self.convert_to_bin(random_value,True) return addr_bits def get_data(self): @@ -273,6 +270,7 @@ class functional(simulation): if(is_addr): expected_value = self.addr_size else: + expected_value = self.word_size for i in range (expected_value - len(new_value)): new_value = "0" + new_value @@ -288,9 +286,7 @@ class functional(simulation): self.stim = stimuli(self.sf,self.corner) #Write include statements - self.sram_sp_file = "{}sram.sp".format(OPTS.openram_temp) - shutil.copy(self.sp_file, self.sram_sp_file) - self.stim.write_include(self.sram_sp_file) + self.stim.write_include(self.sp_file) #Write Vdd/Gnd statements self.sf.write("\n* Global Power Supplies\n") @@ -307,9 +303,17 @@ class functional(simulation): for bit in range(self.word_size): sig_name="{0}{1}_{2} ".format(self.dout_name, port, bit) self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(port, bit, sig_name, self.load)) + + # Write important signals to stim file + self.sf.write("\n\n* Important signals for debug\n") + self.sf.write("* bl: {}\n".format(self.bl_name)) + self.sf.write("* br: {}\n".format(self.br_name)) + self.sf.write("* s_en: {}\n".format(self.sen_name)) + self.sf.write("* q: {}\n".format(self.q_name)) + self.sf.write("* qbar: {}\n".format(self.qbar_name)) # Write debug comments to stim file - self.sf.write("\n\n * Sequence of operations\n") + self.sf.write("\n\n* Sequence of operations\n") for comment in self.fn_cycle_comments: self.sf.write("*{}\n".format(comment)) @@ -369,4 +373,117 @@ class functional(simulation): self.stim.write_control(self.cycle_times[-1] + self.period) self.sf.close() + # FIXME: refactor to share with delay.py + def add_graph_exclusions(self): + """Exclude portions of SRAM from timing graph which are not relevant""" + + # other initializations can only be done during analysis when a bit has been selected + # for testing. + self.sram.bank.graph_exclude_precharge() + self.sram.graph_exclude_addr_dff() + self.sram.graph_exclude_data_dff() + self.sram.graph_exclude_ctrl_dffs() + self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() + + # FIXME: refactor to share with delay.py + def create_graph(self): + """Creates timing graph to generate the timing paths for the SRAM output.""" + + self.sram.bank.bitcell_array.init_graph_params() # Removes previous bit exclusions + # Does wordline=0 and column=0 just for debug names + self.sram.bank.bitcell_array.graph_exclude_bits(0, 0) + + # Generate new graph every analysis as edges might change depending on test bit + self.graph = graph_util.timing_graph() + self.sram_spc_name = "X{}".format(self.sram.name) + self.sram.build_graph(self.graph,self.sram_spc_name,self.pins) + # FIXME: refactor to share with delay.py + def set_internal_spice_names(self): + """Sets important names for characterization such as Sense amp enable and internal bit nets.""" + + # For now, only testing these using first read port. + port = self.read_ports[0] + self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), + '{}{}_{}'.format(self.dout_name, port, 0).lower()) + + self.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(2,"s_en name = {}".format(self.sen_name)) + + self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths, port) + debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + + self.q_name,self.qbar_name = self.get_bit_name() + debug.info(2,"q name={}\nqbar name={}".format(self.q_name,self.qbar_name)) + + def get_bit_name(self): + """ Get a bit cell name """ + (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) + storage_names = cell_inst.mod.get_storage_net_names() + debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" + "supported for characterization. Storage nets={}").format(storage_names)) + q_name = cell_name+'.'+str(storage_names[0]) + qbar_name = cell_name+'.'+str(storage_names[1]) + + return (q_name,qbar_name) + + # FIXME: refactor to share with delay.py + def get_sen_name(self, paths): + """ + Gets the signal name associated with the sense amp enable from input paths. + Only expects a single path to contain the sen signal name. + """ + + sa_mods = factory.get_mods(OPTS.sense_amp) + # Any sense amp instantiated should be identical, any change to that + # will require some identification to determine the mod desired. + debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") + enable_name = sa_mods[0].get_enable_name() + sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) + return sen_name + + # FIXME: refactor to share with delay.py + def get_bl_name(self, paths, port): + """Gets the signal name associated with the bitlines in the bank.""" + + cell_mod = factory.create(module_type=OPTS.bitcell) + cell_bl = cell_mod.get_bl_name(port) + cell_br = cell_mod.get_br_name(port) + + bl_found = False + # Only a single path should contain a single s_en name. Anything else is an error. + bl_names = [] + exclude_set = self.get_bl_name_search_exclusions() + for int_net in [cell_bl, cell_br]: + bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) + + return bl_names[0], bl_names[1] + + def get_bl_name_search_exclusions(self): + """Gets the mods as a set which should be excluded while searching for name.""" + + # Exclude the RBL as it contains bitcells which are not in the main bitcell array + # so it makes the search awkward + return set(factory.get_mods(OPTS.replica_bitline)) + + def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): + """ + Finds a single alias for the int_net in given paths. + More or less hits cause an error + """ + + net_found = False + for path in paths: + aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) + if net_found and len(aliases) >= 1: + debug.error('Found multiple paths with {} net.'.format(int_net),1) + elif len(aliases) > 1: + debug.error('Found multiple {} nets in single path.'.format(int_net),1) + elif not net_found and len(aliases) == 1: + path_net_name = aliases[0] + net_found = True + if not net_found: + debug.error("Could not find {} net in timing paths.".format(int_net),1) + + return path_net_name + diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index aea8f1cd..657c3609 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -25,9 +25,6 @@ class simulation(): self.word_size = self.sram.word_size self.addr_size = self.sram.addr_size self.write_size = self.sram.write_size - self.num_cols = self.sram.num_cols - self.num_rows = self.sram.num_rows - self.num_banks = self.sram.num_banks self.sp_file = spfile self.all_ports = self.sram.all_ports @@ -262,19 +259,21 @@ class simulation(): t_current+self.period) elif op == "partial_write": comment = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)".format(word, - addr, - wmask, - port, - int(t_current / self.period), - t_current, - t_current + self.period) + addr, + wmask, + port, + int(t_current / self.period), + t_current, + t_current + self.period) else: comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, - addr, - port, - int(t_current/self.period), - t_current, - t_current+self.period) + addr, + port, + int(t_current/self.period), + t_current, + t_current+self.period) + + return comment def gen_pin_names(self, port_signal_names, port_info, abits, dbits): diff --git a/compiler/characterizer/sram_op.py b/compiler/characterizer/sram_op.py new file mode 100644 index 00000000..58999ca0 --- /dev/null +++ b/compiler/characterizer/sram_op.py @@ -0,0 +1,15 @@ +# 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. +# + +from enum import Enum + +class sram_op(Enum): + READ_ZERO = 0 + READ_ONE = 1 + WRITE_ZERO = 2 + WRITE_ONE = 3 diff --git a/compiler/characterizer/worst_case.py b/compiler/characterizer/worst_case.py deleted file mode 100644 index 1d4c095e..00000000 --- a/compiler/characterizer/worst_case.py +++ /dev/null @@ -1,84 +0,0 @@ -# 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 sys,re,shutil -import debug -import tech -import math -from .stimuli import * -from .trim_spice import * -from .charutils import * -import utils -from globals import OPTS -from .delay import delay - -class worst_case(delay): - """Functions to test for the worst case delay in a target SRAM - - The current worst case determines a feasible period for the SRAM then tests - several bits and record the delay and differences between the bits. - - """ - - def __init__(self, sram, spfile, corner): - delay.__init__(self,sram,spfile,corner) - - - def analyze(self,probe_address, probe_data, slews, loads): - """ - Main function to test the delays of different bits. - """ - debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 , - "Bit testing does not currently support multiport.") - #Dict to hold all characterization values - char_sram_data = {} - - self.set_probe(probe_address, probe_data) - #self.prepare_netlist() - - self.load=max(loads) - self.slew=max(slews) - - # 1) Find a feasible period and it's corresponding delays using the trimmed array. - feasible_delays = self.find_feasible_period() - - # 2) Find the delays of several bits - test_bits = self.get_test_bits() - bit_delays = self.simulate_for_bit_delays(test_bits) - - for i in range(len(test_bits)): - debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i])) - - def simulate_for_bit_delays(self, test_bits): - """Simulates the delay of the sram of over several bits.""" - bit_delays = [{} for i in range(len(test_bits))] - - #Assumes a bitcell with only 1 rw port. (6t, port 0) - port = 0 - self.targ_read_ports = [self.read_ports[port]] - self.targ_write_ports = [self.write_ports[port]] - - for i in range(len(test_bits)): - (bit_addr, bit_data) = test_bits[i] - self.set_probe(bit_addr, bit_data) - debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data)) - (success, results)=self.run_delay_simulation() - debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data)) - bit_delays[i] = results[port] - - return bit_delays - - - def get_test_bits(self): - """Statically determines address and bit values to test""" - #First and last address, first middle, and last bit. Last bit is repeated twice with different data position. - bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size] - data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1] - #Return them in a tuple form - return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))] - - diff --git a/compiler/globals.py b/compiler/globals.py index ac7bf9e8..0b262430 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -169,11 +169,12 @@ def setup_bitcell(): # If we have non-1rw ports, # and the user didn't over-ride the bitcell manually, # figure out the right bitcell to use - if (OPTS.bitcell=="bitcell" and OPTS.replica_bitcell=="replica_bitcell"): + if (OPTS.bitcell=="bitcell"): if (OPTS.num_rw_ports==1 and OPTS.num_w_ports==0 and OPTS.num_r_ports==0): OPTS.bitcell = "bitcell" OPTS.replica_bitcell = "replica_bitcell" + OPTS.dummy_bitcell = "dummy_bitcell" else: ports = "" if OPTS.num_rw_ports>0: @@ -185,21 +186,26 @@ def setup_bitcell(): OPTS.bitcell = "bitcell_"+ports OPTS.replica_bitcell = "replica_bitcell_"+ports - - # See if a custom bitcell exists - from importlib import find_loader - try: - __import__(OPTS.bitcell) - __import__(OPTS.replica_bitcell) - except ImportError: - # Use the pbitcell if we couldn't find a custom bitcell - # or its custom replica bitcell - # Use the pbitcell (and give a warning if not in unit test mode) - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - if not OPTS.is_unit_test: - debug.warning("Using the parameterized bitcell which may have suboptimal density.") + OPTS.dummy_bitcell = "dummy_bitcell_"+ports + else: + OPTS.replica_bitcell = "replica_" + OPTS.bitcell + OPTS.replica_bitcell = "dummy_" + OPTS.bitcell + # See if bitcell exists + from importlib import find_loader + try: + __import__(OPTS.bitcell) + __import__(OPTS.replica_bitcell) + __import__(OPTS.dummy_bitcell) + except ImportError: + # Use the pbitcell if we couldn't find a custom bitcell + # or its custom replica bitcell + # Use the pbitcell (and give a warning if not in unit test mode) + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell = "replica_pbitcell" + OPTS.replica_bitcell = "dummy_pbitcell" + if not OPTS.is_unit_test: + debug.warning("Using the parameterized bitcell which may have suboptimal density.") debug.info(1,"Using bitcell: {}".format(OPTS.bitcell)) diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index f4f1cabe..613a3381 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -40,6 +40,7 @@ class bank(design.design): design.design.__init__(self, name) debug.info(2, "create sram of size {0} with {1} words".format(self.word_size,self.num_words)) + # The local control signals are gated when we have bank select logic, # so this prefix will be added to all of the input signals to create # the internal gated signals. @@ -57,8 +58,8 @@ class bank(design.design): def create_netlist(self): self.compute_sizes() - self.add_pins() self.add_modules() + self.add_pins() # Must create the replica bitcell array first self.create_instances() @@ -80,10 +81,14 @@ class bank(design.design): """ Adding pins for Bank module""" for port in self.read_ports: for bit in range(self.word_size): - self.add_pin("dout{0}_{1}".format(port,bit),"OUT") + self.add_pin("dout{0}_{1}".format(port,bit),"OUTPUT") + for port in self.read_ports: + self.add_pin(self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]),"OUTPUT") + for port in self.read_ports: + self.add_pin(self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]),"INPUT") for port in self.write_ports: for bit in range(self.word_size): - self.add_pin("din{0}_{1}".format(port,bit),"IN") + self.add_pin("din{0}_{1}".format(port,bit),"INPUT") for port in self.all_ports: for bit in range(self.addr_size): self.add_pin("addr{0}_{1}".format(port,bit),"INPUT") @@ -95,7 +100,7 @@ class bank(design.design): self.add_pin("bank_sel{}".format(port),"INPUT") for port in self.read_ports: self.add_pin("s_en{0}".format(port), "INPUT") - for port in self.read_ports: + for port in self.all_ports: self.add_pin("p_en_bar{0}".format(port), "INPUT") for port in self.write_ports: self.add_pin("w_en{0}".format(port), "INPUT") @@ -113,6 +118,7 @@ class bank(design.design): for port in self.all_ports: self.route_bitlines(port) + self.route_rbl(port) self.route_port_address(port) self.route_column_address_lines(port) self.route_control_lines(port) @@ -121,6 +127,20 @@ class bank(design.design): self.route_supplies() + def route_rbl(self,port): + """ Route the rbl_bl and rbl_wl """ + + if self.port_data[port].has_rbl(): + bl_pin_name = self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) + bl_pin = self.bitcell_array_inst.get_pin(bl_pin_name) + self.add_layout_pin(text="rbl_bl{0}".format(port), + layer=bl_pin.layer, + offset=bl_pin.ll(), + height=bl_pin.height(), + width=bl_pin.width()) + + + def route_bitlines(self, port): """ Route the bitlines depending on the port type rw, w, or r. """ @@ -154,11 +174,17 @@ class bank(design.design): # The center point for these cells are the upper-right corner of # the bitcell array. - # The decoder/driver logic is placed on the right and mirrored on Y-axis. - # The write/sense/precharge/mux is placed on the top and mirrored on the X-axis. - + # The port address decoder/driver logic is placed on the right and mirrored on Y-axis. + # The port data write/sense/precharge/mux is placed on the top and mirrored on the X-axis. self.bitcell_array_top = self.bitcell_array.height - self.bitcell_array_right = self.bitcell_array.width + self.m1_width + self.m2_gap + self.bitcell_array_right = self.bitcell_array.width + + # These are the offsets of the main array (excluding dummy and replica rows/cols) + self.main_bitcell_array_top = self.bitcell_array.bitcell_array_inst.uy() + # Just past the dummy column + self.main_bitcell_array_left = self.bitcell_array.bitcell_array_inst.lx() + # Just past the dummy row and replica row + self.main_bitcell_array_bottom = self.bitcell_array.bitcell_array_inst.by() self.compute_instance_port0_offsets() if len(self.all_ports)==2: @@ -167,7 +193,7 @@ class bank(design.design): def compute_instance_port0_offsets(self): """ - Compute the instance offsets for port0. + Compute the instance offsets for port0 on the left/bottom of the bank. """ port = 0 @@ -178,25 +204,27 @@ class bank(design.design): # LOWER RIGHT QUADRANT # Below the bitcell array - self.port_data_offsets[port] = vector(0,0) + if self.port_data[port].has_rbl(): + self.port_data_offsets[port] = vector(self.main_bitcell_array_left - self.bitcell_array.cell.width,0) + else: + self.port_data_offsets[port] = vector(self.main_bitcell_array_left,0) # UPPER LEFT QUADRANT # To the left of the bitcell array - # The wordline driver is placed to the right of the main decoder width. x_offset = self.m2_gap + self.port_address.width - self.port_address_offsets[port] = vector(-x_offset,0) + self.port_address_offsets[port] = vector(-x_offset,self.main_bitcell_array_bottom) # LOWER LEFT QUADRANT - # Place the col decoder left aligned with wordline driver plus halfway under row decoder - # Place the col decoder left aligned with row decoder (x_offset doesn't change) - # Below the bitcell array with well spacing + # Place the col decoder left aligned with wordline driver + # This is also placed so that it's supply rails do not align with the SRAM-level + # control logic to allow control signals to easily pass over in M3 + # by placing 1/2 a cell pitch down x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.m2_gap + self.column_decoder.height + y_offset = 0.5*self.dff.height + self.column_decoder.height else: y_offset = 0 - y_offset += 2*drc("well_to_well") self.column_decoder_offsets[port] = vector(-x_offset,-y_offset) # Bank select gets placed below the column decoder (x_offset doesn't change) @@ -210,7 +238,7 @@ class bank(design.design): def compute_instance_port1_offsets(self): """ - Compute the instance offsets for port1 on the top of the bank. + Compute the instance offsets for port1 on the right/top of the bank. """ port=1 @@ -220,24 +248,22 @@ class bank(design.design): # UPPER LEFT QUADRANT # Above the bitcell array - self.port_data_offsets[port] = vector(0,self.bitcell_array_top) + self.port_data_offsets[port] = vector(self.main_bitcell_array_left, self.bitcell_array_top) # LOWER RIGHT QUADRANT # To the left of the bitcell array - # The wordline driver is placed to the right of the main decoder width. x_offset = self.bitcell_array_right + self.port_address.width + self.m2_gap - self.port_address_offsets[port] = vector(x_offset,0) + self.port_address_offsets[port] = vector(x_offset,self.main_bitcell_array_bottom) # UPPER RIGHT QUADRANT - # Place the col decoder right aligned with wordline driver plus halfway under row decoder + # Place the col decoder right aligned with wordline driver # Above the bitcell array with a well spacing x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width if self.col_addr_size > 0: x_offset += self.column_decoder.width + self.col_addr_bus_width - y_offset = self.bitcell_array_top + self.m2_gap + self.column_decoder.height + y_offset = self.bitcell_array_top + 0.5*self.dff.height + self.column_decoder.height else: y_offset = self.bitcell_array_top - y_offset += 2*drc("well_to_well") self.column_decoder_offsets[port] = vector(x_offset,y_offset) # Bank select gets placed above the column decoder (x_offset doesn't change) @@ -253,16 +279,12 @@ class bank(design.design): self.compute_instance_offsets() - # UPPER RIGHT QUADRANT self.place_bitcell_array(self.bitcell_array_offset) - # LOWER RIGHT QUADRANT self.place_port_data(self.port_data_offsets) - # UPPER LEFT QUADRANT self.place_port_address(self.port_address_offsets) - # LOWER LEFT QUADRANT self.place_column_decoder(self.column_decoder_offsets) self.place_bank_select(self.bank_select_offsets) @@ -280,22 +302,17 @@ class bank(design.design): debug.check(self.num_rows*self.num_cols==self.word_size*self.num_words,"Invalid bank sizes.") debug.check(self.addr_size==self.col_addr_size + self.row_addr_size,"Invalid address break down.") - # Width for the vdd/gnd rails - self.supply_rail_width = 4*self.m2_width - # FIXME: This spacing should be width dependent... - self.supply_rail_pitch = self.supply_rail_width + 4*self.m2_space - # The order of the control signals on the control bus: self.input_control_signals = [] port_num = 0 for port in range(OPTS.num_rw_ports): - self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)]) + self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "rbl_wl{}".format(port_num)]) port_num += 1 for port in range(OPTS.num_w_ports): - self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num)]) + self.input_control_signals.append(["wl_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num)]) port_num += 1 for port in range(OPTS.num_r_ports): - self.input_control_signals.append(["wl_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num)]) + self.input_control_signals.append(["wl_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "rbl_wl{}".format(port_num)]) port_num += 1 # Number of control lines in the bus for each port @@ -329,15 +346,10 @@ class bank(design.design): # create arrays of bitline and bitline_bar names for read, write, or all ports self.bitcell = factory.create(module_type="bitcell") - self.bl_names = self.bitcell.list_all_bl_names() - self.br_names = self.bitcell.list_all_br_names() - self.wl_names = self.bitcell.list_all_wl_names() - self.bitline_names = self.bitcell.list_all_bitline_names() - - self.bitcell_array = factory.create(module_type="bitcell_array", - cols=self.num_cols, - rows=self.num_rows) - self.add_mod(self.bitcell_array) + self.bl_names = self.bitcell.get_all_bl_names() + self.br_names = self.bitcell.get_all_br_names() + self.wl_names = self.bitcell.get_all_wl_names() + self.bitline_names = self.bitcell.get_all_bitline_names() self.port_data = [] for port in self.all_ports: @@ -352,7 +364,41 @@ class bank(design.design): cols=self.num_cols, rows=self.num_rows) self.add_mod(self.port_address) + + + # The number of replica lines depends on the port configuration + rbl_counts = [self.read_ports.count(p) for p in self.all_ports] + self.num_rbl = sum(rbl_counts) + # The replica array indices always start at 0, so this will map them to + # the correct SRAM port + # (e.g. if port 0 is w, then port 1 will use RBL 0 in replica bitcell array + # because write ports don't use an RBL) + self.port_rbl_map = {} + index = 0 + for (i,num) in enumerate(rbl_counts): + if num>0: + self.port_rbl_map[i]=index + index += 1 + if len(rbl_counts)<2: + rbl_counts.append(0) + + + # Which bitcell port should be used in the RBL + # For now (since only 2 ports), if port 0 is not a read port, skip it in the RBLs + bitcell_ports=list(range(len(self.read_ports))) + if 0 not in self.read_ports: + bitcell_ports = [x+1 for x in bitcell_ports] + + self.bitcell_array = factory.create(module_type="replica_bitcell_array", + cols=self.num_cols, + rows=self.num_rows, + left_rbl=rbl_counts[0], + right_rbl=rbl_counts[1], + bitcell_ports=bitcell_ports) + self.add_mod(self.bitcell_array) + + if(self.num_banks > 1): self.bank_select = factory.create(module_type="bank_select") self.add_mod(self.bank_select) @@ -361,17 +407,24 @@ class bank(design.design): def create_bitcell_array(self): """ Creating Bitcell Array """ - self.bitcell_array_inst=self.add_inst(name="bitcell_array", + self.bitcell_array_inst=self.add_inst(name="replica_bitcell_array", mod=self.bitcell_array) - temp = [] for col in range(self.num_cols): for bitline in self.bitline_names: - temp.append(bitline+"_{0}".format(col)) + temp.append("{0}_{1}".format(bitline,col)) + for rbl in range(self.num_rbl): + rbl_bl_name=self.bitcell_array.get_rbl_bl_name(rbl) + temp.append(rbl_bl_name) + rbl_br_name=self.bitcell_array.get_rbl_br_name(rbl) + temp.append(rbl_br_name) for row in range(self.num_rows): for wordline in self.wl_names: - temp.append(wordline+"_{0}".format(row)) + temp.append("{0}_{1}".format(wordline,row)) + for rbl in range(self.num_rbl): + rbl_wl_name=self.bitcell_array.get_rbl_wl_name(rbl) + temp.append(rbl_wl_name) temp.append("vdd") temp.append("gnd") self.connect_inst(temp) @@ -391,6 +444,11 @@ class bank(design.design): mod=self.port_data[port]) temp = [] + if self.port_data[port].has_rbl(): + rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) + rbl_br_name=self.bitcell_array.get_rbl_br_name(self.port_rbl_map[port]) + temp.append(rbl_bl_name) + temp.append(rbl_br_name) for col in range(self.num_cols): temp.append("{0}_{1}".format(self.bl_names[port],col)) temp.append("{0}_{1}".format(self.br_names[port],col)) @@ -405,8 +463,7 @@ class bank(design.design): temp.extend(sel_names) if port in self.read_ports: temp.append("s_en{0}".format(port)) - if port in self.read_ports: - temp.append("p_en_bar{0}".format(port)) + temp.append("p_en_bar{0}".format(port)) if port in self.write_ports: temp.append("w_en{0}".format(port)) for bit in range(self.num_wmasks): @@ -440,7 +497,7 @@ class bank(design.design): temp.append("addr{0}_{1}".format(port,bit+self.col_addr_size)) temp.append("wl_en{0}".format(port)) for row in range(self.num_rows): - temp.append(self.wl_names[port]+"_{0}".format(row)) + temp.append("{0}_{1}".format(self.wl_names[port],row)) temp.extend(["vdd", "gnd"]) self.connect_inst(temp) @@ -457,7 +514,7 @@ class bank(design.design): # The address flop and decoder are aligned in the x coord. for port in self.all_ports: - if port%2 == 1: + if port%2: mirror = "MY" else: mirror = "R0" @@ -469,16 +526,16 @@ class bank(design.design): Create a 2:4 or 3:8 column address decoder. """ - dff = factory.create(module_type="dff") + self.dff = factory.create(module_type="dff") if self.col_addr_size == 0: return elif self.col_addr_size == 1: - self.column_decoder = factory.create(module_type="pinvbuf", height=dff.height) + self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height) elif self.col_addr_size == 2: - self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height) elif self.col_addr_size == 3: - self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=dff.height) + self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height) else: # No error checking before? debug.error("Invalid column decoder?",-1) @@ -560,8 +617,8 @@ class bank(design.design): bank_sel_signals = ["clk_buf", "w_en", "s_en", "p_en_bar", "bank_sel"] gated_bank_sel_signals = ["gated_clk_buf", "gated_w_en", "gated_s_en", "gated_p_en_bar"] elif self.port_id[port] == "w": - bank_sel_signals = ["clk_buf", "w_en", "bank_sel"] - gated_bank_sel_signals = ["gated_clk_buf", "gated_w_en"] + bank_sel_signals = ["clk_buf", "w_en", "p_en_bar", "bank_sel"] + gated_bank_sel_signals = ["gated_clk_buf", "gated_w_en", "gated_p_en_bar"] else: bank_sel_signals = ["clk_buf", "s_en", "p_en_bar", "bank_sel"] gated_bank_sel_signals = ["gated_clk_buf", "gated_s_en", "gated_p_en_bar"] @@ -615,9 +672,9 @@ class bank(design.design): # Port 0 # The bank is at (0,0), so this is to the left of the y-axis. # 2 pitches on the right for vias/jogs to access the inputs - control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_width, self.min_y_offset) + control_bus_offset = vector(-self.m2_pitch * self.num_control_lines[0] - self.m2_pitch, self.min_y_offset) # The control bus is routed up to two pitches below the bitcell array - control_bus_length = -2*self.m1_pitch - self.min_y_offset + control_bus_length = self.main_bitcell_array_bottom - self.min_y_offset - 2*self.m1_pitch self.bus_xoffset[0] = self.create_bus(layer="metal2", pitch=self.m2_pitch, offset=control_bus_offset, @@ -629,14 +686,14 @@ class bank(design.design): # Port 1 if len(self.all_ports)==2: # The other control bus is routed up to two pitches above the bitcell array - control_bus_length = self.max_y_offset - self.bitcell_array_top - 2*self.m1_pitch - control_bus_offset = vector(self.bitcell_array_right, + control_bus_length = self.max_y_offset - self.main_bitcell_array_top - 2*self.m1_pitch + control_bus_offset = vector(self.bitcell_array_right + self.m2_pitch, self.max_y_offset - control_bus_length) - + # The bus for the right port is reversed so that the rbl_wl is closest to the array self.bus_xoffset[1] = self.create_bus(layer="metal2", pitch=self.m2_pitch, offset=control_bus_offset, - names=self.control_signals[1], + names=list(reversed(self.control_signals[1])), length=control_bus_length, vertical=True, make_pins=(self.num_banks==1)) @@ -645,12 +702,22 @@ class bank(design.design): def route_port_data_to_bitcell_array(self, port): """ Routing of BL and BR between port data and bitcell array """ + # Connect the regular bitlines inst2 = self.port_data_inst[port] inst1 = self.bitcell_array_inst inst1_bl_name = self.bl_names[port]+"_{}" inst1_br_name = self.br_names[port]+"_{}" + self.connect_bitlines(inst1=inst1, inst2=inst2, num_bits=self.num_cols, inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) + + # Connect the replica bitlines + if self.port_data[port].has_rbl(): + rbl_bl_name=self.bitcell_array.get_rbl_bl_name(self.port_rbl_map[port]) + rbl_br_name=self.bitcell_array.get_rbl_br_name(self.port_rbl_map[port]) + self.connect_bitline(inst1, inst2, rbl_bl_name, "rbl_bl") + self.connect_bitline(inst1, inst2, rbl_br_name, "rbl_br") + def route_port_data_out(self, port): @@ -710,12 +777,9 @@ class bank(design.design): route_map = list(zip(bottom_names, top_names)) self.create_horizontal_channel_route(route_map, offset) - - def connect_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", - inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + def connect_bitline(self, inst1, inst2, inst1_name, inst2_name): """ - Connect the bl and br of two modules. + Connect two pins of two modules. This assumes that they have sufficient space to create a jog in the middle between the two modules (if needed). """ @@ -723,23 +787,34 @@ class bank(design.design): # determine top and bottom automatically. # since they don't overlap, we can just check the bottom y coordinate. if inst1.by() < inst2.by(): - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name) - (top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name) + (bottom_inst, bottom_name) = (inst1, inst1_name) + (top_inst, top_name) = (inst2, inst2_name) else: - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name) - (top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name) + (bottom_inst, bottom_name) = (inst2, inst2_name) + (top_inst, top_name) = (inst1, inst1_name) + + bottom_pin = bottom_inst.get_pin(bottom_name) + top_pin = top_inst.get_pin(top_name) + debug.check(bottom_pin.layer == top_pin.layer, "Pin layers do not match.") + + bottom_loc = bottom_pin.uc() + top_loc = top_pin.bc() + + yoffset = 0.5*(top_loc.y+bottom_loc.y) + self.add_path(top_pin.layer,[bottom_loc, vector(bottom_loc.x,yoffset), + vector(top_loc.x,yoffset), top_loc]) + + + def connect_bitlines(self, inst1, inst2, num_bits, + inst1_bl_name="bl_{}", inst1_br_name="br_{}", + inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + """ + Connect the bl and br of two modules. + """ for col in range(num_bits): - bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col)).uc() - bottom_br = bottom_inst.get_pin(bottom_br_name.format(col)).uc() - top_bl = top_inst.get_pin(top_bl_name.format(col)).bc() - top_br = top_inst.get_pin(top_br_name.format(col)).bc() - - yoffset = 0.5*(top_bl.y+bottom_bl.y) - self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset), - vector(top_bl.x,yoffset), top_bl]) - self.add_path("metal2",[bottom_br, vector(bottom_br.x,yoffset), - vector(top_br.x,yoffset), top_br]) + self.connect_bitline(inst1, inst2, inst1_bl_name.format(col), inst2_bl_name.format(col)) + self.connect_bitline(inst1, inst2, inst1_br_name.format(col), inst2_br_name.format(col)) def route_port_address(self, port): @@ -868,21 +943,26 @@ class bank(design.design): read_inst = 0 connection = [] + connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc())) + if port in self.read_ports: - connection.append((self.prefix+"p_en_bar{}".format(port), self.port_data_inst[port].get_pin("p_en_bar").lc())) - + rbl_wl_name = self.bitcell_array.get_rbl_wl_name(self.port_rbl_map[port]) + connection.append((self.prefix+"rbl_wl{}".format(port), self.bitcell_array_inst.get_pin(rbl_wl_name).lc())) + if port in self.write_ports: connection.append((self.prefix+"w_en{}".format(port), self.port_data_inst[port].get_pin("w_en").lc())) if port in self.read_ports: connection.append((self.prefix+"s_en{}".format(port), self.port_data_inst[port].get_pin("s_en").lc())) - + for (control_signal, pin_pos) in connection: + control_mid_pos = self.bus_xoffset[port][control_signal] control_pos = vector(self.bus_xoffset[port][control_signal].x ,pin_pos.y) - self.add_path("metal1", [control_pos, pin_pos]) + self.add_wire(("metal1","via1","metal2"), [control_mid_pos, control_pos, pin_pos]) self.add_via_center(layers=("metal1", "via1", "metal2"), offset=control_pos) + # clk to wordline_driver control_signal = self.prefix+"wl_en{}".format(port) if port%2: diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 75c831d7..90405d75 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -73,32 +73,32 @@ class bitcell_array(design.design): self.DRC_LVS() def add_pins(self): - row_list = self.cell.list_all_wl_names() - column_list = self.cell.list_all_bitline_names() + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() for col in range(self.column_size): for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col)) + self.add_pin(cell_column+"_{0}".format(col), "INOUT") for row in range(self.row_size): for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row)) - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin(cell_row+"_{0}".format(row), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Add the modules used in this design """ self.cell = factory.create(module_type="bitcell") self.add_mod(self.cell) - def list_bitcell_pins(self, col, row): + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = [] - pin_names = self.cell.list_all_bitline_names() + pin_names = self.cell.get_all_bitline_names() for pin in pin_names: bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.list_all_wl_names() + pin_names = self.cell.get_all_wl_names() for pin in pin_names: bitcell_pins.append(pin+"_{0}".format(row)) bitcell_pins.append("vdd") @@ -115,13 +115,13 @@ class bitcell_array(design.design): name = "bit_r{0}_c{1}".format(row, col) self.cell_inst[row,col]=self.add_inst(name=name, mod=self.cell) - self.connect_inst(self.list_bitcell_pins(col, row)) + self.connect_inst(self.get_bitcell_pins(col, row)) def add_layout_pins(self): """ Add the layout pins """ - row_list = self.cell.list_all_wl_names() - column_list = self.cell.list_all_bitline_names() + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() for col in range(self.column_size): for cell_column in column_list: @@ -215,4 +215,4 @@ class bitcell_array(design.design): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col] \ No newline at end of file + return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col] diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index bbd78a13..1d2226b1 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -39,7 +39,7 @@ class control_logic(design.design): self.num_cols = word_size*words_per_row self.num_words = num_rows*words_per_row - self.enable_delay_chain_resizing = True + 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. @@ -73,10 +73,8 @@ class control_logic(design.design): def add_pins(self): """ Add the pins to the control logic module. """ - for pin in self.input_list + ["clk"]: - self.add_pin(pin,"INPUT") - for pin in self.output_list: - self.add_pin(pin,"OUTPUT") + 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") @@ -96,6 +94,12 @@ class control_logic(design.design): size=4, height=dff_height) self.add_mod(self.and2) + + self.rbl_driver = factory.create(module_type="pbuf", + size=self.num_cols, + height=dff_height) + self.add_mod(self.rbl_driver) + # clk_buf drives a flop for every address and control bit # plus about 5 fanouts for the control logic @@ -115,16 +119,16 @@ class control_logic(design.design): self.add_mod(self.wl_en_driver) # w_en drives every write driver - self.w_en_driver = factory.create(module_type="pdriver", - fanout=self.word_size+8, - height=dff_height) - self.add_mod(self.w_en_driver) + self.wen_and2 = factory.create(module_type="pand2", + size=self.word_size+8, + height=dff_height) + self.add_mod(self.wen_and2) # s_en drives every sense amp - self.s_en_driver = factory.create(module_type="pdriver", - fanout=self.word_size, - height=dff_height) - self.add_mod(self.s_en_driver) + self.sen_and2 = factory.create(module_type="pand2", + size=self.word_size, + height=dff_height) + self.add_mod(self.sen_and2) # used to generate inverted signals with low fanout self.inv = factory.create(module_type="pinv", @@ -132,53 +136,58 @@ class control_logic(design.design): height=dff_height) self.add_mod(self.inv) - # p_en_bar drives every column in the bicell array + # p_en_bar drives every column in the bitcell array self.p_en_bar_driver = factory.create(module_type="pdriver", neg_polarity=True, fanout=self.num_cols, height=dff_height) self.add_mod(self.p_en_bar_driver) + - if (self.port_type == "rw") or (self.port_type == "r"): - from importlib import reload - self.delay_chain_resized = False - c = reload(__import__(OPTS.replica_bitline)) - replica_bitline = getattr(c, OPTS.replica_bitline) - bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage)) - #Use a model to determine the delays with that heuristic - if OPTS.use_tech_delay_chain_size: #Use tech parameters if set. - fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage] - debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list)) - self.replica_bitline = factory.create(module_type="replica_bitline", - delay_fanout_list=fanout_list, - bitcell_loads=bitcell_loads) - if self.sram != None: #Calculate model value even for specified sizes - self.set_sen_wl_delays() + + # if (self.port_type == "rw") or (self.port_type == "r"): + # from importlib import reload + # self.delay_chain_resized = False + # c = reload(__import__(OPTS.replica_bitline)) + # replica_bitline = getattr(c, OPTS.replica_bitline) + # bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage)) + # #Use a model to determine the delays with that heuristic + # if OPTS.use_tech_delay_chain_size: #Use tech parameters if set. + # fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage] + # debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list)) + # self.replica_bitline = factory.create(module_type="replica_bitline", + # delay_fanout_list=fanout_list, + # bitcell_loads=bitcell_loads) + # if self.sram != None: #Calculate model value even for specified sizes + # self.set_sen_wl_delays() - else: #Otherwise, use a heuristic and/or model based sizing. - #First use a heuristic - delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size() - self.replica_bitline = factory.create(module_type="replica_bitline", - 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(): - #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) + # else: #Otherwise, use a heuristic and/or model based sizing. + # #First use a heuristic + # delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size() + # self.replica_bitline = factory.create(module_type="replica_bitline", + # 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(): + # #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. - # 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, - # bitcell_loads=bitcell_loads) + # #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, + # # bitcell_loads=bitcell_loads) - self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing - self.delay_chain_resized = True - - self.add_mod(self.replica_bitline) + # 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.") + self.delay_chain=factory.create(module_type="delay_chain", + 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): """Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """ @@ -312,8 +321,13 @@ class control_logic(design.design): # List of input control signals if self.port_type == "rw": self.input_list = ["csb", "web"] + self.rbl_list = ["rbl_bl"] + elif self.port_type == "r": + self.input_list = ["csb"] + self.rbl_list = ["rbl_bl"] else: self.input_list = ["csb"] + self.rbl_list = [] if self.port_type == "rw": self.dff_output_list = ["cs_bar", "cs", "we_bar", "we"] @@ -332,11 +346,12 @@ class control_logic(design.design): # Outputs to the bank if self.port_type == "rw": - self.output_list = ["s_en", "w_en", "p_en_bar"] + self.output_list = ["rbl_wl", "s_en", "w_en"] elif self.port_type == "r": - self.output_list = ["s_en", "p_en_bar"] + self.output_list = ["rbl_wl", "s_en"] else: self.output_list = ["w_en"] + self.output_list.append("p_en_bar") self.output_list.append("wl_en") self.output_list.append("clk_buf") @@ -360,12 +375,12 @@ class control_logic(design.design): self.create_wlen_row() if (self.port_type == "rw") or (self.port_type == "w"): self.create_wen_row() - if self.port_type == "rw": - self.create_rbl_in_row() - if (self.port_type == "rw") or (self.port_type == "r"): - self.create_pen_row() + if (self.port_type == "rw") or (self.port_type == "r"): + self.create_rbl_row() self.create_sen_row() - self.create_rbl() + self.create_delay() + self.create_pen_row() + def place_instances(self): @@ -392,20 +407,20 @@ class control_logic(design.design): row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_wen_row(row) - height = self.w_en_inst.uy() - control_center_y = self.w_en_inst.uy() + height = self.w_en_gate_inst.uy() + control_center_y = self.w_en_gate_inst.uy() row += 1 - if self.port_type == "rw": - self.place_rbl_in_row(row) + if (self.port_type == "rw") or (self.port_type == "r"): + self.place_rbl_row(row) row += 1 + self.place_pen_row(row) + row += 1 if (self.port_type == "rw") or (self.port_type == "r"): - self.place_pen_row(row) - row += 1 self.place_sen_row(row) row += 1 - self.place_rbl(row) - height = self.rbl_inst.uy() - control_center_y = self.rbl_inst.by() + self.place_delay(row) + height = self.delay_inst.uy() + control_center_y = self.delay_inst.by() # This offset is used for placement of the control logic in the SRAM level. self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) @@ -415,7 +430,7 @@ class control_logic(design.design): # 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.rbl_inst.rx() , self.width) + self.width = max(self.delay_inst.rx() , self.width) self.width += self.m2_pitch def route_all(self): @@ -426,33 +441,29 @@ class control_logic(design.design): if (self.port_type == "rw") or (self.port_type == "w"): self.route_wen() if (self.port_type == "rw") or (self.port_type == "r"): - self.route_rbl_in() - self.route_pen() + self.route_rbl() self.route_sen() + self.route_pen() self.route_clk_buf() self.route_gated_clk_bar() self.route_gated_clk_buf() self.route_supply() - def create_rbl(self): + def create_delay(self): """ Create the replica bitline """ - if self.port_type == "r": - input_name = "gated_clk_bar" - else: - input_name = "rbl_in" - self.rbl_inst=self.add_inst(name="replica_bitline", - mod=self.replica_bitline) - self.connect_inst([input_name, "pre_s_en", "vdd", "gnd"]) + self.delay_inst=self.add_inst(name="delay_chain", + mod=self.delay_chain) + self.connect_inst(["rbl_bl", "pre_s_en", "vdd", "gnd"]) - def place_rbl(self,row): + def place_delay(self,row): """ Place the replica bitline """ 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 - offset = vector(0, y_off) - self.rbl_inst.place(offset) + offset = vector(self.delay_chain.width, y_off) + self.delay_inst.place(offset, mirror="MY") def create_clk_buf_row(self): @@ -588,44 +599,31 @@ class control_logic(design.design): self.connect_vertical_bus(wlen_map, self.wl_en_inst, self.rail_offsets) self.connect_output(self.wl_en_inst, "Z", "wl_en") - def create_rbl_in_row(self): - - # input: gated_clk_bar, we_bar, output: rbl_in - self.rbl_in_inst=self.add_inst(name="and2_rbl_in", - mod=self.and2) - self.connect_inst(["gated_clk_bar", "we_bar", "rbl_in", "vdd", "gnd"]) + def create_rbl_row(self): - def place_rbl_in_row(self,row): + self.rbl_inst=self.add_inst(name="rbl_driver", + mod=self.rbl_driver) + # input: gated_clk_bar, output: rbl_wl + self.connect_inst(["gated_clk_bar", "rbl_wl", "vdd", "gnd"]) + + def place_rbl_row(self,row): x_off = self.control_x_offset (y_off,mirror)=self.get_offset(row) offset = vector(x_off, y_off) - self.rbl_in_inst.place(offset, mirror) + self.rbl_inst.place(offset, mirror) - self.row_end_inst.append(self.rbl_in_inst) + self.row_end_inst.append(self.rbl_inst) - def route_rbl_in(self): + def route_rbl(self): """ Connect the logic for the rbl_in generation """ - if self.port_type == "rw": - input_name = "we_bar" - # Connect the NAND gate inputs to the bus - rbl_in_map = zip(["A", "B"], ["gated_clk_bar", "we_bar"]) - self.connect_vertical_bus(rbl_in_map, self.rbl_in_inst, self.rail_offsets) - - - # Connect the output of the precharge enable to the RBL input - if self.port_type == "rw": - out_pos = self.rbl_in_inst.get_pin("Z").center() - else: - out_pos = vector(self.rail_offsets["gated_clk_bar"].x, self.rbl_inst.by()-3*self.m2_pitch) - in_pos = self.rbl_inst.get_pin("en").center() - mid1 = vector(in_pos.x,out_pos.y) - self.add_wire(("metal3","via2","metal2"),[out_pos, mid1, in_pos]) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=out_pos) - self.add_via_center(layers=("metal2","via2","metal3"), - offset=out_pos) + rbl_in_map = zip(["A"], ["gated_clk_bar"]) + self.connect_vertical_bus(rbl_in_map, self.rbl_inst, self.rail_offsets) + self.connect_output(self.rbl_inst, "Z", "rbl_wl") + + # Input from RBL goes to the delay line for futher delay + self.copy_layout_pin(self.delay_inst, "in", "rbl_bl") def create_pen_row(self): input_name = "gated_clk_buf" @@ -639,7 +637,6 @@ class control_logic(design.design): def place_pen_row(self,row): x_off = self.control_x_offset (y_off,mirror)=self.get_offset(row) - offset = vector(x_off,y_off) self.p_en_bar_inst.place(offset, mirror) @@ -647,17 +644,21 @@ class control_logic(design.design): def route_pen(self): in_map = zip(["A"], ["gated_clk_buf"]) - self.connect_vertical_bus(in_map, self.p_en_bar_inst, self.rail_offsets) + self.connect_vertical_bus(in_map, self.p_en_bar_inst, self.rail_offsets) self.connect_output(self.p_en_bar_inst, "Z", "p_en_bar") def create_sen_row(self): """ Create the sense enable buffer. """ - # BUFFER FOR S_EN - # input: pre_s_en, output: s_en - self.s_en_inst=self.add_inst(name="buf_s_en", - mod=self.s_en_driver) - self.connect_inst(["pre_s_en", "s_en", "vdd", "gnd"]) + if self.port_type=="rw": + input_name = "we_bar" + else: + input_name = "cs_bar" + # GATE FOR S_EN + self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", + mod=self.sen_and2) + self.connect_inst(["pre_s_en", input_name, "s_en", "vdd", "gnd"]) + def place_sen_row(self,row): """ @@ -668,19 +669,27 @@ class control_logic(design.design): (y_off,mirror)=self.get_offset(row) offset = vector(x_off, y_off) - self.s_en_inst.place(offset, mirror) + self.s_en_gate_inst.place(offset, mirror) - self.row_end_inst.append(self.s_en_inst) + self.row_end_inst.append(self.s_en_gate_inst) def route_sen(self): + + if self.port_type=="rw": + input_name = "we_bar" + else: + input_name = "cs_bar" + + sen_map = zip(["B"], [input_name]) + self.connect_vertical_bus(sen_map, self.s_en_gate_inst, self.rail_offsets) - out_pos = self.rbl_inst.get_pin("out").bc() - in_pos = self.s_en_inst.get_pin("A").lc() + out_pos = self.delay_inst.get_pin("out").bc() + in_pos = self.s_en_gate_inst.get_pin("A").lc() mid1 = vector(out_pos.x,in_pos.y) self.add_wire(("metal1","via1","metal2"),[out_pos, mid1,in_pos]) - self.connect_output(self.s_en_inst, "Z", "s_en") + self.connect_output(self.s_en_gate_inst, "Z", "s_en") def create_wen_row(self): @@ -690,34 +699,33 @@ class control_logic(design.design): else: # No we for write-only reports, so use cs input_name = "cs" - - # BUFFER FOR W_EN - self.w_en_inst = self.add_inst(name="buf_w_en_buf", - mod=self.w_en_driver) - self.connect_inst([input_name, "w_en", "vdd", "gnd"]) + # GATE THE W_EN + self.w_en_gate_inst = self.add_inst(name="w_en_and", + mod=self.wen_and2) + self.connect_inst([input_name, "gated_clk_bar", "w_en", "vdd", "gnd"]) + def place_wen_row(self,row): x_off = self.ctrl_dff_inst.width + self.internal_bus_width (y_off,mirror)=self.get_offset(row) offset = vector(x_off, y_off) - self.w_en_inst.place(offset, mirror) + self.w_en_gate_inst.place(offset, mirror) - self.row_end_inst.append(self.w_en_inst) + self.row_end_inst.append(self.w_en_gate_inst) def route_wen(self): - if self.port_type == "rw": input_name = "we" else: # No we for write-only reports, so use cs input_name = "cs" - wen_map = zip(["A"], [input_name]) - self.connect_vertical_bus(wen_map, self.w_en_inst, self.rail_offsets) + wen_map = zip(["A", "B"], [input_name, "gated_clk_bar"]) + self.connect_vertical_bus(wen_map, self.w_en_gate_inst, self.rail_offsets) - self.connect_output(self.w_en_inst, "Z", "w_en") + 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", @@ -794,8 +802,8 @@ class control_logic(design.design): self.add_path("metal1", [row_loc, pin_loc]) if (self.port_type == "rw") or (self.port_type == "r"): - self.copy_layout_pin(self.rbl_inst,"gnd") - self.copy_layout_pin(self.rbl_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") @@ -821,7 +829,7 @@ class control_logic(design.design): # height=pin.height(), # width=pin.width()) - pin=self.rbl_inst.get_pin("out") + pin=self.delay_inst.get_pin("out") self.add_label_pin(text="out", layer=pin.layer, offset=pin.ll(), @@ -882,7 +890,7 @@ class control_logic(design.design): last_stage_rise = stage_effort_list[-1].is_rise #Replica bitline stage, rbl_in -(rbl)-> pre_s_en - stage2_cout = self.s_en_driver.get_cin() + 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 @@ -895,6 +903,7 @@ class control_logic(design.design): def get_wl_sen_delays(self): """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) wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts) @@ -903,6 +912,7 @@ class control_logic(design.design): def analytical_delay(self, corner, slew, load): """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() @@ -930,8 +940,10 @@ class control_logic(design.design): return stage_effort_list def get_clk_buf_cin(self): - """Get the loads that are connected to the buffered clock. - Includes all the DFFs and some logic.""" + """ + Get the loads that are connected to the buffered clock. + Includes all the DFFs and some logic. + """ #Control logic internal load int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin() @@ -943,6 +955,7 @@ class control_logic(design.design): def get_gated_clk_bar_cin(self): """Get intermediates net gated_clk_bar's capacitance""" + total_cin = 0 total_cin += self.wl_en_driver.get_cin() if self.port_type == 'rw': @@ -951,4 +964,7 @@ class control_logic(design.design): 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) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index 6a54c1a3..bc932a26 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -57,10 +57,10 @@ class delay_chain(design.design): def add_pins(self): """ Add the pins of the delay chain""" - self.add_pin("in") - self.add_pin("out") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("in", "INPUT") + self.add_pin("out", "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): self.inv = factory.create(module_type="pinv", route_output=False) diff --git a/compiler/modules/dff_array.py b/compiler/modules/dff_array.py index 3ea3fc7f..32b36765 100644 --- a/compiler/modules/dff_array.py +++ b/compiler/modules/dff_array.py @@ -54,13 +54,13 @@ class dff_array(design.design): def add_pins(self): for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col)) + self.add_pin(self.get_din_name(row,col), "INPUT") for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col)) - self.add_pin("clk") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin("clk", "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_dff_array(self): self.dff_insts={} diff --git a/compiler/modules/dff_buf.py b/compiler/modules/dff_buf.py index 9533d647..9359329a 100644 --- a/compiler/modules/dff_buf.py +++ b/compiler/modules/dff_buf.py @@ -75,12 +75,12 @@ class dff_buf(design.design): def add_pins(self): - self.add_pin("D") - self.add_pin("Q") - self.add_pin("Qb") - self.add_pin("clk") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("D", "INPUT") + self.add_pin("Q", "OUTPUT") + self.add_pin("Qb", "OUTPUT") + self.add_pin("clk", "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_instances(self): self.dff_inst=self.add_inst(name="dff_buf_dff", diff --git a/compiler/modules/dff_buf_array.py b/compiler/modules/dff_buf_array.py index 60bdae02..df36b2aa 100644 --- a/compiler/modules/dff_buf_array.py +++ b/compiler/modules/dff_buf_array.py @@ -55,14 +55,14 @@ class dff_buf_array(design.design): def add_pins(self): for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col)) + self.add_pin(self.get_din_name(row,col), "INPUT") for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col)) - self.add_pin(self.get_dout_bar_name(row,col)) - self.add_pin("clk") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_bar_name(row,col), "OUTPUT") + self.add_pin("clk", "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): self.dff = factory.create(module_type="dff_buf", diff --git a/compiler/modules/dff_inv_array.py b/compiler/modules/dff_inv_array.py index 1e6754b9..051dd237 100644 --- a/compiler/modules/dff_inv_array.py +++ b/compiler/modules/dff_inv_array.py @@ -59,14 +59,14 @@ class dff_inv_array(design.design): def add_pins(self): for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_din_name(row,col)) + self.add_pin(self.get_din_name(row,col), "INPUT") for row in range(self.rows): for col in range(self.columns): - self.add_pin(self.get_dout_name(row,col)) - self.add_pin(self.get_dout_bar_name(row,col)) - self.add_pin("clk") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin(self.get_dout_name(row,col), "OUTPUT") + self.add_pin(self.get_dout_bar_name(row,col), "OUTPUT") + self.add_pin("clk", "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_dff_array(self): self.dff_insts={} diff --git a/compiler/modules/dummy_array.py b/compiler/modules/dummy_array.py index 0790367f..f1f433ce 100644 --- a/compiler/modules/dummy_array.py +++ b/compiler/modules/dummy_array.py @@ -15,13 +15,14 @@ class dummy_array(design.design): """ Generate a dummy row/column for the replica array. """ - def __init__(self, cols, rows, name): + def __init__(self, cols, rows, mirror=0, name=""): design.design.__init__(self, name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) self.column_size = cols self.row_size = rows + self.mirror = mirror self.create_netlist() if not OPTS.netlist_only: @@ -46,7 +47,7 @@ class dummy_array(design.design): for row in range(self.row_size): name = "dummy_r{0}_c{1}".format(row, col) - if row % 2: + if (row+self.mirror) % 2: tempy = yoffset + self.dummy_cell.height dir_key = "MX" else: @@ -65,16 +66,16 @@ class dummy_array(design.design): self.DRC_LVS() def add_pins(self): - row_list = self.cell.list_all_wl_names() - column_list = self.cell.list_all_bitline_names() + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() for col in range(self.column_size): for cell_column in column_list: - self.add_pin(cell_column+"_{0}".format(col)) + self.add_pin(cell_column+"_{0}".format(col), "INOUT") for row in range(self.row_size): for cell_row in row_list: - self.add_pin(cell_row+"_{0}".format(row)) - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin(cell_row+"_{0}".format(row), "INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Add the modules used in this design """ @@ -83,16 +84,16 @@ class dummy_array(design.design): self.cell = factory.create(module_type="bitcell") - def list_bitcell_pins(self, col, row): + def get_bitcell_pins(self, col, row): """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ bitcell_pins = [] - pin_names = self.cell.list_all_bitline_names() + pin_names = self.cell.get_all_bitline_names() for pin in pin_names: bitcell_pins.append(pin+"_{0}".format(col)) - pin_names = self.cell.list_all_wl_names() + pin_names = self.cell.get_all_wl_names() for pin in pin_names: bitcell_pins.append(pin+"_{0}".format(row)) bitcell_pins.append("vdd") @@ -109,13 +110,13 @@ class dummy_array(design.design): name = "bit_r{0}_c{1}".format(row, col) self.cell_inst[row,col]=self.add_inst(name=name, mod=self.dummy_cell) - self.connect_inst(self.list_bitcell_pins(col, row)) + self.connect_inst(self.get_bitcell_pins(col, row)) def add_layout_pins(self): """ Add the layout pins """ - row_list = self.cell.list_all_wl_names() - column_list = self.cell.list_all_bitline_names() + row_list = self.cell.get_all_wl_names() + column_list = self.cell.get_all_bitline_names() for col in range(self.column_size): for cell_column in column_list: diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 4f359c95..75eb3345 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -231,12 +231,12 @@ class hierarchical_decoder(design.design): """ Add the module pins """ for i in range(self.num_inputs): - self.add_pin("addr_{0}".format(i)) + self.add_pin("addr_{0}".format(i), "INPUT") for j in range(self.rows): - self.add_pin("decode_{0}".format(j)) - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("decode_{0}".format(j), "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_pre_decoder(self): diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index a8d4797f..bec0ce06 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -26,11 +26,11 @@ class hierarchical_predecode(design.design): def add_pins(self): for k in range(self.number_of_inputs): - self.add_pin("in_{0}".format(k)) + self.add_pin("in_{0}".format(k), "INPUT") for i in range(self.number_of_outputs): - self.add_pin("out_{0}".format(i)) - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("out_{0}".format(i), "OUTPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): """ Add the INV and NAND gate modules """ diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index bfa34710..0a624c60 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -83,8 +83,7 @@ class port_address(design.design): driver_name = "wl_{}".format(row) self.copy_layout_pin(self.wordline_driver_inst, driver_name) - # FIXME: Is this still inverted!? - self.copy_layout_pin(self.wordline_driver_inst, "en_bar", "wl_en") + self.copy_layout_pin(self.wordline_driver_inst, "en", "wl_en") def route_internal(self): for row in range(self.num_rows): diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index e0979955..3e4caf57 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -15,6 +15,7 @@ from globals import OPTS class port_data(design.design): """ Create the data port (column mux, sense amps, write driver, etc.) for the given port number. + Port 0 always has the RBL on the left while port 1 is on the right. """ def __init__(self, sram_config, port, name=""): @@ -80,9 +81,13 @@ class port_data(design.design): def add_pins(self): """ Adding pins for port address module""" + + if self.has_rbl(): + self.add_pin("rbl_bl","INOUT") + self.add_pin("rbl_br","INOUT") for bit in range(self.num_cols): - self.add_pin(self.bl_names[self.port]+"_{0}".format(bit),"INOUT") - self.add_pin(self.br_names[self.port]+"_{0}".format(bit),"INOUT") + self.add_pin("{0}_{1}".format(self.bl_names[self.port], bit),"INOUT") + self.add_pin("{0}_{1}".format(self.br_names[self.port], bit),"INOUT") if self.port in self.read_ports: for bit in range(self.word_size): self.add_pin("dout_{}".format(bit),"OUTPUT") @@ -95,8 +100,7 @@ class port_data(design.design): self.add_pin(pin_name,"INPUT") if self.port in self.read_ports: self.add_pin("s_en", "INPUT") - if self.port in self.read_ports: - self.add_pin("p_en_bar", "INPUT") + self.add_pin("p_en_bar", "INPUT") if self.port in self.write_ports: self.add_pin("w_en", "INPUT") for bit in range(self.num_wmasks): @@ -132,12 +136,14 @@ class port_data(design.design): self.route_sense_amp_to_column_mux_or_precharge_array(self.port) self.route_column_mux_to_precharge_array(self.port) else: - # write_driver -> (column_mux) -> bitcell_array + # write_driver -> (column_mux ->) precharge -> bitcell_array self.route_write_driver_in(self.port) - self.route_write_driver_to_column_mux_or_bitcell_array(self.port) + self.route_write_driver_to_column_mux_or_precharge_array(self.port) + self.route_column_mux_to_precharge_array(self.port) def route_supplies(self): """ Propagate all vdd/gnd pins up to this level for all modules """ + for inst in self.insts: self.copy_power_pins(inst,"vdd") self.copy_power_pins(inst,"gnd") @@ -145,8 +151,10 @@ class port_data(design.design): def add_modules(self): if self.port in self.read_ports: + # Extra column +1 is for RBL + # Precharge will be shifted left if needed self.precharge_array = factory.create(module_type="precharge_array", - columns=self.num_cols, + columns=self.num_cols + 1, bitcell_bl=self.bl_names[self.port], bitcell_br=self.br_names[self.port]) self.add_mod(self.precharge_array) @@ -156,8 +164,17 @@ class port_data(design.design): words_per_row=self.words_per_row) self.add_mod(self.sense_amp_array) else: - self.precharge_array = None + # Precharge is needed when we have a column mux or for byte writes + # to prevent corruption of half-selected cells, so just always add it + # This is a little power inefficient for write ports without a column mux, + # but it is simpler. + self.precharge_array = factory.create(module_type="precharge_array", + columns=self.num_cols, + bitcell_bl=self.bl_names[self.port], + bitcell_br=self.br_names[self.port]) + self.add_mod(self.precharge_array) self.sense_amp_array = None + if self.col_addr_size > 0: self.column_mux_array = factory.create(module_type="column_mux_array", @@ -206,9 +223,9 @@ class port_data(design.design): # create arrays of bitline and bitline_bar names for read, write, or all ports self.bitcell = factory.create(module_type="bitcell") - self.bl_names = self.bitcell.list_all_bl_names() - self.br_names = self.bitcell.list_all_br_names() - self.wl_names = self.bitcell.list_all_wl_names() + self.bl_names = self.bitcell.get_all_bl_names() + self.br_names = self.bitcell.get_all_br_names() + self.wl_names = self.bitcell.get_all_wl_names() def create_precharge_array(self): """ Creating Precharge """ @@ -218,21 +235,32 @@ class port_data(design.design): self.precharge_array_inst = self.add_inst(name="precharge_array{}".format(self.port), mod=self.precharge_array) + temp = [] + # Use left BLs for RBL + if self.has_rbl() and self.port==0: + temp.append("rbl_bl") + temp.append("rbl_br") for bit in range(self.num_cols): temp.append(self.bl_names[self.port]+"_{0}".format(bit)) temp.append(self.br_names[self.port]+"_{0}".format(bit)) + # Use right BLs for RBL + if self.has_rbl() and self.port==1: + temp.append("rbl_bl") + temp.append("rbl_br") temp.extend(["p_en_bar", "vdd"]) self.connect_inst(temp) def place_precharge_array(self, offset): """ Placing Precharge """ + self.precharge_array_inst.place(offset=offset, mirror="MX") def create_column_mux_array(self): """ Creating Column Mux when words_per_row > 1 . """ + self.column_mux_array_inst = self.add_inst(name="column_mux_array{}".format(self.port), mod=self.column_mux_array) @@ -248,7 +276,6 @@ class port_data(design.design): temp.append("gnd") self.connect_inst(temp) - def place_column_mux_array(self, offset): """ Placing Column Mux when words_per_row > 1 . """ @@ -272,7 +299,7 @@ class port_data(design.design): else: temp.append(self.bl_names[self.port]+"_out_{0}".format(bit)) temp.append(self.br_names[self.port]+"_out_{0}".format(bit)) - + temp.extend(["s_en", "vdd", "gnd"]) self.connect_inst(temp) @@ -292,7 +319,7 @@ class port_data(design.design): temp.append("din_{}".format(bit)) for bit in range(self.word_size): - if (self.words_per_row == 1): + if (self.words_per_row == 1): temp.append(self.bl_names[self.port]+"_{0}".format(bit)) temp.append(self.br_names[self.port]+"_{0}".format(bit)) else: @@ -305,6 +332,7 @@ class port_data(design.design): else: temp.append("w_en") temp.extend(["vdd", "gnd"]) + self.connect_inst(temp) @@ -339,21 +367,32 @@ class port_data(design.design): vertical_port_order.append(self.sense_amp_array_inst) vertical_port_order.append(self.write_driver_array_inst) + # Add one column for the the RBL + if self.has_rbl() and self.port==0: + x_offset = self.bitcell.width + else: + x_offset = 0 + vertical_port_offsets = 4*[None] - self.width = 0 + self.width = x_offset self.height = 0 for i,p in enumerate(vertical_port_order): if p==None: continue self.height += (p.height + self.m2_gap) self.width = max(self.width, p.width) - vertical_port_offsets[i]=vector(0,self.height) + vertical_port_offsets[i]=vector(x_offset,self.height) # Reversed order self.write_driver_offset = vertical_port_offsets[3] self.sense_amp_offset = vertical_port_offsets[2] self.column_mux_offset = vertical_port_offsets[1] self.precharge_offset = vertical_port_offsets[0] + # Shift the precharge left if port 0 + if self.precharge_offset and self.port==0: + self.precharge_offset -= vector(x_offset,0) + + @@ -399,30 +438,39 @@ class port_data(design.design): inst1 = self.column_mux_array_inst inst2 = self.precharge_array_inst - self.connect_bitlines(inst1, inst2, self.num_cols) + if self.has_rbl() and self.port==0: + self.connect_bitlines(inst1, inst2, self.num_cols, inst2_start_bit=1) + else: + self.connect_bitlines(inst1, inst2, self.num_cols) def route_sense_amp_to_column_mux_or_precharge_array(self, port): """ Routing of BL and BR between sense_amp and column mux or precharge array """ inst2 = self.sense_amp_array_inst - + if self.col_addr_size>0: # Sense amp is connected to the col mux inst1 = self.column_mux_array_inst inst1_bl_name = "bl_out_{}" inst1_br_name = "br_out_{}" + start_bit = 0 else: # Sense amp is directly connected to the precharge array inst1 = self.precharge_array_inst inst1_bl_name = "bl_{}" inst1_br_name = "br_{}" - - self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) + if self.has_rbl() and self.port==0: + start_bit=1 + else: + start_bit=0 - def route_write_driver_to_column_mux_or_bitcell_array(self, port): - """ Routing of BL and BR between sense_amp and column mux or bitcell array """ + + self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit) + + def route_write_driver_to_column_mux_or_precharge_array(self, port): + """ Routing of BL and BR between sense_amp and column mux or precharge array """ inst2 = self.write_driver_array_inst if self.col_addr_size>0: @@ -430,12 +478,19 @@ class port_data(design.design): inst1 = self.column_mux_array_inst inst1_bl_name = "bl_out_{}" inst1_br_name = "br_out_{}" + start_bit = 0 else: - # Write driver is directly connected to the bitcell array - return + # Sense amp is directly connected to the precharge array + inst1 = self.precharge_array_inst + inst1_bl_name = "bl_{}" + inst1_br_name = "br_{}" + if self.has_rbl() and self.port==0: + start_bit=1 + else: + start_bit=0 self.channel_route_bitlines(inst1=inst1, inst2=inst2, num_bits=self.word_size, - inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name) + inst1_bl_name=inst1_bl_name, inst1_br_name=inst1_br_name, inst1_start_bit=start_bit) def route_write_driver_to_sense_amp(self, port): """ Routing of BL and BR between write driver and sense amp """ @@ -451,16 +506,25 @@ class port_data(design.design): def route_bitline_pins(self): """ Add the bitline pins for the given port """ + # Connect one bitline to the RBL and offset the indices for the other BLs + if self.has_rbl() and self.port==0: + self.copy_layout_pin(self.precharge_array_inst, "bl_0", "rbl_bl") + self.copy_layout_pin(self.precharge_array_inst, "br_0", "rbl_br") + bit_offset=1 + elif self.has_rbl() and self.port==1: + self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(self.num_cols), "rbl_bl") + self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(self.num_cols), "rbl_br") + bit_offset=0 + else: + bit_offset=0 + + for bit in range(self.num_cols): - if self.port in self.read_ports: - self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(bit)) - self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(bit)) - elif self.column_mux_array_inst: - self.copy_layout_pin(self.column_mux_array_inst, "bl_{}".format(bit)) - self.copy_layout_pin(self.column_mux_array_inst, "br_{}".format(bit)) + if self.precharge_array_inst: + self.copy_layout_pin(self.precharge_array_inst, "bl_{}".format(bit+bit_offset), "bl_{}".format(bit)) + self.copy_layout_pin(self.precharge_array_inst, "br_{}".format(bit+bit_offset), "br_{}".format(bit)) else: - self.copy_layout_pin(self.write_driver_array_inst, "bl_{}".format(bit)) - self.copy_layout_pin(self.write_driver_array_inst, "br_{}".format(bit)) + debug.error("Didn't find precharge arra.") def route_control_pins(self): """ Add the control pins: s_en, p_en_bar, w_en """ @@ -477,8 +541,8 @@ class port_data(design.design): def channel_route_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", - inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0, + inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0): """ Route the bl and br of two modules using the channel router. """ @@ -486,26 +550,27 @@ class port_data(design.design): # determine top and bottom automatically. # since they don't overlap, we can just check the bottom y coordinate. if inst1.by() < inst2.by(): - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name) - (top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name) + (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) + (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) else: - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name) - (top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name) + (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) + (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) # Channel route each mux separately since we don't minimize the number # of tracks in teh channel router yet. If we did, we could route all the bits at once! offset = bottom_inst.ul() + vector(0,self.m1_pitch) for bit in range(num_bits): - bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit)), bottom_inst.get_pin(bottom_br_name.format(bit))] - top_names = [top_inst.get_pin(top_bl_name.format(bit)), top_inst.get_pin(top_br_name.format(bit))] + bottom_names = [bottom_inst.get_pin(bottom_bl_name.format(bit+bottom_start_bit)), bottom_inst.get_pin(bottom_br_name.format(bit+bottom_start_bit))] + top_names = [top_inst.get_pin(top_bl_name.format(bit+top_start_bit)), top_inst.get_pin(top_br_name.format(bit+top_start_bit))] route_map = list(zip(bottom_names, top_names)) self.create_horizontal_channel_route(route_map, offset) def connect_bitlines(self, inst1, inst2, num_bits, - inst1_bl_name="bl_{}", inst1_br_name="br_{}", - inst2_bl_name="bl_{}", inst2_br_name="br_{}"): + inst1_bl_name="bl_{}", inst1_br_name="br_{}", inst1_start_bit=0, + inst2_bl_name="bl_{}", inst2_br_name="br_{}", inst2_start_bit=0): + """ Connect the bl and br of two modules. This assumes that they have sufficient space to create a jog @@ -515,17 +580,17 @@ class port_data(design.design): # determine top and bottom automatically. # since they don't overlap, we can just check the bottom y coordinate. if inst1.by() < inst2.by(): - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst1, inst1_bl_name, inst1_br_name) - (top_inst, top_bl_name, top_br_name) = (inst2, inst2_bl_name, inst2_br_name) + (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) + (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) else: - (bottom_inst, bottom_bl_name, bottom_br_name) = (inst2, inst2_bl_name, inst2_br_name) - (top_inst, top_bl_name, top_br_name) = (inst1, inst1_bl_name, inst1_br_name) + (bottom_inst, bottom_bl_name, bottom_br_name, bottom_start_bit) = (inst2, inst2_bl_name, inst2_br_name, inst2_start_bit) + (top_inst, top_bl_name, top_br_name, top_start_bit) = (inst1, inst1_bl_name, inst1_br_name, inst1_start_bit) for col in range(num_bits): - bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col)).uc() - bottom_br = bottom_inst.get_pin(bottom_br_name.format(col)).uc() - top_bl = top_inst.get_pin(top_bl_name.format(col)).bc() - top_br = top_inst.get_pin(top_br_name.format(col)).bc() + bottom_bl = bottom_inst.get_pin(bottom_bl_name.format(col+bottom_start_bit)).uc() + bottom_br = bottom_inst.get_pin(bottom_br_name.format(col+bottom_start_bit)).uc() + top_bl = top_inst.get_pin(top_bl_name.format(col+top_start_bit)).bc() + top_br = top_inst.get_pin(top_br_name.format(col+top_start_bit)).bc() yoffset = 0.5*(top_bl.y+bottom_bl.y) self.add_path("metal2",[bottom_bl, vector(bottom_bl.x,yoffset), @@ -537,3 +602,6 @@ class port_data(design.design): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" if self.precharge_array_inst: self.graph_inst_exclude.add(self.precharge_array_inst) + + def has_rbl(self): + return self.port in self.read_ports diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index bf45afd5..2d98ba14 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -35,10 +35,11 @@ class precharge_array(design.design): def add_pins(self): """Adds pins for spice file""" for i in range(self.columns): - self.add_pin("bl_{0}".format(i)) - self.add_pin("br_{0}".format(i)) - self.add_pin("en_bar") - self.add_pin("vdd") + # These are outputs from the precharge only + self.add_pin("bl_{0}".format(i), "OUTPUT") + self.add_pin("br_{0}".format(i), "OUTPUT") + self.add_pin("en_bar", "INPUT") + self.add_pin("vdd", "POWER") def create_netlist(self): self.add_modules() @@ -115,4 +116,4 @@ class precharge_array(design.design): #Assume single port precharge_en_cin = self.pc_cell.get_en_cin() return precharge_en_cin*self.columns - \ No newline at end of file + diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 0d72891d..89a8ff04 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -17,18 +17,30 @@ import dummy_array class replica_bitcell_array(design.design): """ - Creates a bitcell arrow of cols x rows and then adds the replica and dummy columns - and rows for one or two read ports. Replica columns are on the left and right, respectively. + Creates a bitcell arrow of cols x rows and then adds the replica + and dummy columns and rows. Replica columns are on the left and + right, respectively and connected to the given bitcell ports. Dummy are the outside columns/rows with WL and BL tied to gnd. - Requires a regular bitcell array, replica bitcell, and dummy bitcell (Bl/BR disconnected). + Requires a regular bitcell array, replica bitcell, and dummy + bitcell (Bl/BR disconnected). """ - def __init__(self, cols, rows, name): + def __init__(self, cols, rows, left_rbl, right_rbl, bitcell_ports, name): design.design.__init__(self, name) debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) self.add_comment("rows: {0} cols: {1}".format(rows, cols)) self.column_size = cols self.row_size = rows + self.left_rbl = left_rbl + self.right_rbl = right_rbl + self.bitcell_ports = bitcell_ports + + debug.check(left_rbl+right_rbl==len(self.read_ports),"Invalid number of RBLs for port configuration.") + debug.check(left_rbl+right_rbl==len(self.bitcell_ports),"Bitcell ports must match total RBLs.") + + # Two dummy rows/cols plus replica for each port + self.extra_rows = 2 + left_rbl + right_rbl + self.extra_cols = 2 + left_rbl + right_rbl self.create_netlist() if not OPTS.netlist_only: @@ -78,127 +90,201 @@ class replica_bitcell_array(design.design): rows=self.row_size) self.add_mod(self.bitcell_array) - # Replica bitline - self.replica_column = factory.create(module_type="replica_column", - rows=self.row_size + 4) - self.add_mod(self.replica_column) + # Replica bitlines + self.replica_columns = {} + for bit in range(self.left_rbl+self.right_rbl): + if bit1 port) + for port in range(self.left_rbl): + # Make names for all RBLs + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + # Keep track of the pin that is the RBL + self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] + self.replica_col_wl_names.extend(wl_names) + # Regular WLs + self.replica_col_wl_names.extend(self.bitcell_array_wl_names) + # Right port WLs (one dummy for each port when we allow >1 port) + for port in range(self.left_rbl,self.left_rbl+self.right_rbl): + # Make names for all RBLs + wl_names=["rbl_{0}_{1}".format(self.cell.get_wl_name(x),port) for x in range(len(self.all_ports))] + # Keep track of the pin that is the RBL + self.rbl_wl_names[port]=wl_names[self.bitcell_ports[port]] + self.replica_col_wl_names.extend(wl_names) + self.replica_col_wl_names.extend(["{0}_top".format(x) for x in self.dummy_cell_wl_names]) + + # Left/right dummy columns are connected identically to the replica column self.dummy_col_wl_names = self.replica_col_wl_names - - - self.add_pin_list(self.bl_names) - self.add_pin_list([x+"_0" for x in self.replica_bl_names]) - self.add_pin_list([x+"_1" for x in self.replica_bl_names]) - self.add_pin_list([x for x in self.replica_col_wl_names if not x.startswith("dummy")]) - self.add_pin("vdd") - self.add_pin("gnd") + + # Per port bitline names + self.replica_bl_names = {} + self.replica_wl_names = {} + # Array of all port bitline names + for port in range(self.left_rbl+self.right_rbl): + left_names=["rbl_{0}_{1}".format(self.cell.get_bl_name(x),port) for x in range(len(self.all_ports))] + right_names=["rbl_{0}_{1}".format(self.cell.get_br_name(x),port) for x in range(len(self.all_ports))] + # Keep track of the left pins that are the RBL + self.rbl_bl_names[port]=left_names[self.bitcell_ports[port]] + self.rbl_br_names[port]=right_names[self.bitcell_ports[port]] + # Interleave the left and right lists + bl_names = [x for t in zip(left_names, right_names) for x in t] + self.replica_bl_names[port] = bl_names + + wl_names = ["rbl_{0}_{1}".format(x,port) for x in self.cell.get_all_wl_names()] + #wl_names[port] = "rbl_wl{}".format(port) + self.replica_wl_names[port] = wl_names + + + # External pins + self.add_pin_list(self.bitcell_array_bl_names, "INOUT") + # Need to sort by port order since dictionary values may not be in order + bl_names = [self.rbl_bl_names[x] for x in sorted(self.rbl_bl_names.keys())] + br_names = [self.rbl_br_names[x] for x in sorted(self.rbl_br_names.keys())] + for (bl_name,br_name) in zip(bl_names,br_names): + self.add_pin(bl_name,"OUTPUT") + self.add_pin(br_name,"OUTPUT") + self.add_pin_list(self.bitcell_array_wl_names, "INPUT") + # Need to sort by port order since dictionary values may not be in order + wl_names = [self.rbl_wl_names[x] for x in sorted(self.rbl_wl_names.keys())] + for pin_name in wl_names: + self.add_pin(pin_name,"INPUT") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def create_instances(self): """ Create the module instances used in this design """ supplies = ["vdd", "gnd"] + + # Used for names/dimensions only + self.cell = factory.create(module_type="bitcell") + # Main array self.bitcell_array_inst=self.add_inst(name="bitcell_array", mod=self.bitcell_array) - self.connect_inst(self.bitcell_array.pins) + self.connect_inst(self.bitcell_array_bl_names + self.bitcell_array_wl_names + supplies) - # Replica columns (two even if one port for now) - self.replica_col_left_inst=self.add_inst(name="replica_col_left", - mod=self.replica_column) - self.connect_inst([x+"_0" for x in self.replica_bl_names] + self.replica_col_wl_names + supplies) - - self.replica_col_right_inst=self.add_inst(name="replica_col_right", - mod=self.replica_column) - self.connect_inst([x+"_1" for x in self.replica_bl_names] + self.replica_col_wl_names[::-1] + supplies) + # Replica columns + self.replica_col_inst = {} + for port in range(self.left_rbl+self.right_rbl): + self.replica_col_inst[port]=self.add_inst(name="replica_col_{}".format(port), + mod=self.replica_columns[port]) + self.connect_inst(self.replica_bl_names[port] + self.replica_col_wl_names + supplies) - # Replica rows with replica bitcell - self.dummy_row_bottop_inst=self.add_inst(name="dummy_row_bottop", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_0" for x in self.replica_wl_names] + supplies) - self.dummy_row_topbot_inst=self.add_inst(name="dummy_row_topbot", - mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_1" for x in self.replica_wl_names] + supplies) + + # Dummy rows under the bitcell array (connected with with the replica cell wl) + self.dummy_row_replica_inst = {} + for port in range(self.left_rbl+self.right_rbl): + self.dummy_row_replica_inst[port]=self.add_inst(name="dummy_row_{}".format(port), + mod=self.dummy_row) + self.connect_inst(self.dummy_row_bl_names + self.replica_wl_names[port] + supplies) - # Dummy rows without replica bitcell - self.dummy_row_botbot_inst=self.add_inst(name="dummy_row_botbot", + # Top/bottom dummy rows + self.dummy_row_bot_inst=self.add_inst(name="dummy_row_bot", mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_0" for x in self.dummy_wl_names] + supplies) - self.dummy_row_toptop_inst=self.add_inst(name="dummy_row_toptop", + self.connect_inst(self.dummy_row_bl_names + [x+"_bot" for x in self.dummy_cell_wl_names] + supplies) + self.dummy_row_top_inst=self.add_inst(name="dummy_row_top", mod=self.dummy_row) - self.connect_inst(self.dummy_row_bl_names + [x+"_1" for x in self.dummy_wl_names] + supplies) + self.connect_inst(self.dummy_row_bl_names + [x+"_top" for x in self.dummy_cell_wl_names] + supplies) - # Dummy columns + # Left/right Dummy columns self.dummy_col_left_inst=self.add_inst(name="dummy_col_left", mod=self.dummy_col) - self.connect_inst([x+"_0" for x in self.dummy_bl_names] + self.dummy_col_wl_names + supplies) + self.connect_inst([x+"_left" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) self.dummy_col_right_inst=self.add_inst(name="dummy_col_right", mod=self.dummy_col) - self.connect_inst([x+"_1" for x in self.dummy_bl_names] + self.dummy_col_wl_names + supplies) + self.connect_inst([x+"_right" for x in self.dummy_cell_bl_names] + self.dummy_col_wl_names + supplies) def create_layout(self): - self.height = (self.row_size+4)*self.dummy_row.height - self.width = (self.column_size+4)*self.replica_column.width + self.height = (self.row_size+self.extra_rows)*self.dummy_row.height + self.width = (self.column_size+self.extra_cols)*self.cell.width # This is a bitcell x bitcell offset to scale - offset = vector(self.replica_column.width, self.dummy_row.height) + offset = vector(self.cell.width, self.cell.height) self.bitcell_array_inst.place(offset=[0,0]) - self.replica_col_left_inst.place(offset=offset.scale(-1,-2)) - self.replica_col_right_inst.place(offset=offset.scale(0,2)+self.bitcell_array_inst.ur(), - mirror="MX") - - self.dummy_row_toptop_inst.place(offset=offset.scale(0,2)+self.bitcell_array_inst.ul(), - mirror="MX") - self.dummy_row_topbot_inst.place(offset=offset.scale(0,0)+self.bitcell_array_inst.ul()) - self.dummy_row_bottop_inst.place(offset=offset.scale(0,0), - mirror="MX") - self.dummy_row_botbot_inst.place(offset=offset.scale(0,-2)) - self.dummy_col_left_inst.place(offset=offset.scale(-2,-2)) - self.dummy_col_right_inst.place(offset=offset.scale(1,-2)+self.bitcell_array_inst.lr()) + # To the left of the bitcell array + for bit in range(self.left_rbl): + self.replica_col_inst[bit].place(offset=offset.scale(-bit-1,-self.left_rbl-1)) + # To the right of the bitcell array + for bit in range(self.right_rbl): + self.replica_col_inst[self.left_rbl+bit].place(offset=offset.scale(bit,-self.left_rbl-1)+self.bitcell_array_inst.lr()) + - self.translate_all(offset.scale(-2,-2)) + # Far top dummy row (first row above array is NOT flipped) + flip_dummy = self.right_rbl%2 + self.dummy_row_top_inst.place(offset=offset.scale(0,self.right_rbl+flip_dummy)+self.bitcell_array_inst.ul(), + mirror="MX" if flip_dummy else "R0") + # Far bottom dummy row (first row below array IS flipped) + flip_dummy = (self.left_rbl+1)%2 + self.dummy_row_bot_inst.place(offset=offset.scale(0,-self.left_rbl-1+flip_dummy), + mirror="MX" if flip_dummy else "R0") + # Far left dummy col + self.dummy_col_left_inst.place(offset=offset.scale(-self.left_rbl-1,-self.left_rbl-1)) + # Far right dummy col + self.dummy_col_right_inst.place(offset=offset.scale(self.right_rbl,-self.left_rbl-1)+self.bitcell_array_inst.lr()) + + # Replica dummy rows + for bit in range(self.left_rbl): + self.dummy_row_replica_inst[bit].place(offset=offset.scale(0,-bit-bit%2), + mirror="R0" if bit%2 else "MX") + for bit in range(self.right_rbl): + self.dummy_row_replica_inst[self.left_rbl+bit].place(offset=offset.scale(0,bit+bit%2)+self.bitcell_array_inst.ul(), + mirror="MX" if bit%2 else "R0") + + + self.translate_all(offset.scale(-1-self.left_rbl,-1-self.left_rbl)) self.add_layout_pins() @@ -216,90 +302,77 @@ class replica_bitcell_array(design.design): if pin_name.startswith("wl"): pin_list = self.bitcell_array_inst.get_pins(pin_name) for pin in pin_list: - self.add_layout_pin_rect_center(text=pin_name, - layer=pin.layer, - offset=pin.center(), - width=self.width, - height=pin.height()) + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(0,1), + width=self.width, + height=pin.height()) elif pin_name.startswith("bl") or pin_name.startswith("br"): pin_list = self.bitcell_array_inst.get_pins(pin_name) for pin in pin_list: - self.add_layout_pin_rect_center(text=pin_name, - layer=pin.layer, - offset=pin.center(), - width=pin.width(), - height=self.height) + self.add_layout_pin(text=pin_name, + layer=pin.layer, + offset=pin.ll().scale(1,0), + width=pin.width(), + height=self.height) - for index,(side1,side2) in enumerate([("bottop","left"),("topbot","right")]): - inst = getattr(self, "dummy_row_{}_inst".format(side1)) - pin_names = inst.mod.get_pin_names() - for pin_name in pin_names: - if pin_name.startswith("wl"): - pin_list = inst.get_pins(pin_name) - for pin in pin_list: - name = "replica_{0}_{1}".format(pin_name,index) - self.add_layout_pin_rect_center(text=name, - layer=pin.layer, - offset=pin.center(), - width=self.width, - height=pin.height()) + # Replica wordlines + for port in range(self.left_rbl+self.right_rbl): + inst = self.replica_col_inst[port] + for (pin_name,wl_name) in zip(self.cell.get_all_wl_names(),self.replica_wl_names[port]): + # +1 for dummy row + pin_bit = port+1 + # +row_size if above the array + if port>=self.left_rbl: + pin_bit += self.row_size + + pin_name += "_{}".format(pin_bit) + pin = inst.get_pin(pin_name) + if wl_name in self.rbl_wl_names.values(): + self.add_layout_pin(text=wl_name, + layer=pin.layer, + offset=pin.ll().scale(0,1), + width=self.width, + height=pin.height()) - # Replica columns - for index,side in enumerate(["left","right"]): - inst = getattr(self, "replica_col_{}_inst".format(side)) - pin_names = inst.mod.get_pin_names() - for pin_name in pin_names: - if pin_name.startswith("bl") or pin_name.startswith("br"): - pin_list = inst.get_pins(pin_name) - for pin in pin_list: - name = "replica_{0}_{1}".format(pin_name,index) - self.add_layout_pin(text=name, - layer=pin.layer, - offset=pin.ll().scale(1,0), - width=pin.width(), - height=self.height) + + # Replica bitlines + for port in range(self.left_rbl+self.right_rbl): + inst = self.replica_col_inst[port] + for (pin_name, bl_name) in zip(self.cell.get_all_bitline_names(),self.replica_bl_names[port]): + pin = inst.get_pin(pin_name) + if bl_name in self.rbl_bl_names or bl_name in self.rbl_br_names: + name = bl_name + else: + name = "rbl_{0}_{1}".format(pin_name,port) + self.add_layout_pin(text=name, + layer=pin.layer, + offset=pin.ll().scale(1,0), + width=pin.width(), + height=self.height) for pin_name in ["vdd","gnd"]: - for inst in [self.bitcell_array_inst, - self.replica_col_left_inst, self.replica_col_right_inst, - self.dummy_col_left_inst, self.dummy_col_right_inst, - self.dummy_row_toptop_inst, self.dummy_row_topbot_inst, - self.dummy_row_bottop_inst, self.dummy_row_botbot_inst]: + for inst in self.insts: pin_list = inst.get_pins(pin_name) for pin in pin_list: self.add_power_pin(name=pin_name, loc=pin.center(), vertical=True, start_layer=pin.layer) - # Non-pins - for side in ["botbot", "toptop"]: - inst = getattr(self, "dummy_row_{}_inst".format(side)) - pin_names = inst.mod.get_pin_names() - for pin_name in pin_names: - if pin_name.startswith("wl"): - pin_list = inst.get_pins(pin_name) - for pin in pin_list: - self.add_rect_center(layer=pin.layer, - offset=pin.center(), - width=self.width, - height=pin.height()) - - - for side in ["left", "right"]: - inst = getattr(self, "dummy_col_{}_inst".format(side)) - pin_names = inst.mod.get_pin_names() - for pin_name in pin_names: - if pin_name.startswith("b"): - pin_list = inst.get_pins(pin_name) - for pin in pin_list: - self.add_rect_center(layer=pin.layer, - offset=pin.center(), - width=pin.width(), - height=self.height) + def get_rbl_wl_name(self, port): + """ Return the WL for the given RBL port """ + return self.rbl_wl_names[port] + + def get_rbl_bl_name(self, port): + """ Return the BL for the given RBL port """ + return self.rbl_bl_names[port] + def get_rbl_br_name(self, port): + """ Return the BR for the given RBL port """ + return self.rbl_br_names[port] def analytical_delay(self, corner, slew, load): """Returns relative delay of the bitline in the bitcell array""" @@ -307,7 +380,7 @@ class replica_bitcell_array(design.design): #The load being driven/drained is mostly the bitline but could include the sense amp or the column mux. #The load from the bitlines is due to the drain capacitances from all the other bitlines and wire parasitics. drain_load = logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) - wire_unit_load = .05 * drain_load #Wires add 5% to this. + wire_unit_load = 0.05 * drain_load #Wires add 5% to this. bitline_load = (drain_load+wire_unit_load)*self.row_size return [self.cell.analytical_delay(corner, slew, load+bitline_load)] @@ -318,7 +391,7 @@ class replica_bitcell_array(design.design): # Dynamic Power from Bitline bl_wire = self.gen_bl_wire() cell_load = 2 * bl_wire.return_input_cap() - bl_swing = parameter["rbl_height_percentage"] + bl_swing = OPTS.rbl_delay_percentage freq = spice["default_event_rate"] bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing) @@ -346,3 +419,17 @@ class replica_bitcell_array(design.design): bitcell_wl_cin = self.cell.get_wl_cin() total_cin = bitcell_wl_cin * self.column_size return total_cin + + def graph_exclude_bits(self, targ_row, targ_col): + """Excludes bits in column from being added to graph except target""" + self.bitcell_array.graph_exclude_bits(targ_row, targ_col) + + def graph_exclude_replica_col_bits(self): + """Exclude all replica/dummy cells in the replica columns except the replica bit.""" + + for port in range(self.left_rbl+self.right_rbl): + self.replica_columns[port].exclude_all_but_replica() + + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + return self.bitcell_array.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) diff --git a/compiler/modules/replica_bitline.py b/compiler/modules/replica_bitline.py deleted file mode 100644 index 2a441f02..00000000 --- a/compiler/modules/replica_bitline.py +++ /dev/null @@ -1,621 +0,0 @@ -# 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 debug -import design -from tech import drc -import contact -from sram_factory import factory -from vector import vector -from globals import OPTS - -class replica_bitline(design.design): - """ - Generate a module that simulates the delay of control logic - and bit line charging. Stages is the depth of the delay - line and rows is the height of the replica bit loads. - """ - - def __init__(self, name, delay_fanout_list, bitcell_loads): - design.design.__init__(self, name) - - self.bitcell_loads = bitcell_loads - self.delay_fanout_list = delay_fanout_list - if len(delay_fanout_list) == 0 or len(delay_fanout_list)%2 == 1: - debug.error('Delay chain must contain an even amount of stages to maintain polarity.',1) - - self.create_netlist() - if not OPTS.netlist_only: - self.create_layout() - - def create_netlist(self): - self.add_modules() - self.add_pins() - self.create_instances() - - def create_layout(self): - self.calculate_module_offsets() - self.place_instances() - self.route() - self.add_layout_pins() - - self.offset_all_coordinates() - - #self.add_lvs_correspondence_points() - - # Extra pitch on top and right - self.width = self.replica_column_inst.rx() - self.delay_chain_inst.lx() + self.m2_pitch - self.height = max(self.replica_column_inst.uy(), self.delay_chain_inst.uy()) + self.m3_pitch - - self.add_boundary() - self.DRC_LVS() - - def add_pins(self): - for pin in ["en", "out", "vdd", "gnd"]: - self.add_pin(pin) - - def calculate_module_offsets(self): - """ Calculate all the module offsets """ - - # These aren't for instantiating, but we use them to get the dimensions - self.poly_contact_offset = vector(0.5*contact.poly.width,0.5*contact.poly.height) - - # Quadrant 1: Replica bitline and such are not rotated, but they must be placed far enough - # away from the delay chain/inverter with space for three M2 tracks - self.bitcell_offset = vector(0,self.replica_bitcell.height) - self.rbl_offset = self.bitcell_offset - - # Gap between the delay chain and RBL - gap_width = 2*self.m2_pitch - - # Quadrant 4: with some space below it and tracks on the right for vdd/gnd - self.delay_chain_offset = vector(-self.delay_chain.width-gap_width,self.replica_bitcell.height) - - # Will be flipped vertically below the delay chain - # Align it with the inverters in the delay chain to simplify supply connections - self.rbl_inv_offset = self.delay_chain_offset + vector(2*self.inv.width, 0) - - # Placed next to the replica bitcell - self.access_tx_offset = vector(-gap_width-self.access_tx.width-self.inv.width, 0.5*self.inv.height) - - - def add_modules(self): - """ Add the modules for later usage """ - - self.replica_bitcell = factory.create(module_type="replica_bitcell") - self.add_mod(self.replica_bitcell) - - # This is the replica bitline load column that is the height of our array - self.rbl = factory.create(module_type="bitcell_array", - cols=1, - rows=self.bitcell_loads) - self.add_mod(self.rbl) - - # FIXME: The FO and depth of this should be tuned - self.delay_chain = factory.create(module_type="delay_chain", - fanout_list=self.delay_fanout_list) - self.add_mod(self.delay_chain) - - self.inv = factory.create(module_type="pinv") - self.add_mod(self.inv) - - self.access_tx = factory.create(module_type="ptx", - tx_type="pmos") - self.add_mod(self.access_tx) - - def create_instances(self): - """ Create all of the module instances in the logical netlist """ - - # This is the threshold detect inverter on the output of the RBL - self.rbl_inv_inst=self.add_inst(name="rbl_inv", - mod=self.inv) - self.connect_inst(["bl0_0", "out", "vdd", "gnd"]) - - self.tx_inst=self.add_inst(name="rbl_access_tx", - mod=self.access_tx) - # D, G, S, B - self.connect_inst(["vdd", "delayed_en", "bl0_0", "vdd"]) - # add the well and poly contact - - self.delay_chain_inst=self.add_inst(name="delay_chain", - mod=self.delay_chain) - self.connect_inst(["en", "delayed_en", "vdd", "gnd"]) - - self.replica_cell_inst=self.add_inst(name="bitcell", - mod=self.replica_bitcell) - temp = [] - for port in self.all_ports: - temp.append("bl{}_0".format(port)) - temp.append("br{}_0".format(port)) - for port in self.all_ports: - temp.append("delayed_en") - temp.append("vdd") - temp.append("gnd") - self.connect_inst(temp) - - self.replica_column_inst=self.add_inst(name="load", - mod=self.rbl) - - temp = [] - for port in self.all_ports: - temp.append("bl{}_0".format(port)) - temp.append("br{}_0".format(port)) - for wl in range(self.bitcell_loads): - for port in self.all_ports: - temp.append("gnd") - temp.append("vdd") - temp.append("gnd") - self.connect_inst(temp) - - self.wl_list = self.rbl.cell.list_all_wl_names() - self.bl_list = self.rbl.cell.list_all_bl_names() - - def place_instances(self): - """ Add all of the module instances in the logical netlist """ - - # This is the threshold detect inverter on the output of the RBL - self.rbl_inv_inst.place(offset=self.rbl_inv_offset, - rotate=180) - - self.tx_inst.place(self.access_tx_offset) - - self.delay_chain_inst.place(self.delay_chain_offset) - - self.replica_cell_inst.place(offset=self.bitcell_offset, - mirror="MX") - - self.replica_column_inst.place(self.rbl_offset) - - - def route(self): - """ Connect all the signals together """ - self.route_supplies() - self.route_wl() - self.route_access_tx() - - def route_wl(self): - """ Connect the RBL word lines to gnd """ - # Connect the WL and gnd pins directly to the center and right gnd rails - for row in range(self.bitcell_loads): - wl = self.wl_list[0]+"_{}".format(row) - pin = self.replica_column_inst.get_pin(wl) - - # Route the connection to the right so that it doesn't interfere with the cells - # Wordlines may be close to each other when tiled, so gnd connections are routed in opposite directions - pin_right = pin.rc() - pin_extension = pin_right + vector(self.m3_pitch,0) - - if pin.layer != "metal1": - continue - pin_width_ydir = pin.uy()-pin.by() - #Width is set to pin y width to avoid DRC issues with m1 gaps - self.add_path("metal1", [pin_right, pin_extension], pin_width_ydir) - self.add_power_pin("gnd", pin_extension) - - # for multiport, need to short wordlines to each other so they all connect to gnd. - wl_last = self.wl_list[-1]+"_{}".format(row) - pin_last = self.replica_column_inst.get_pin(wl_last) - self.short_wordlines(pin, pin_last, "right", False, row, vector(self.m3_pitch,0)) - - def short_wordlines(self, wl_pin_a, wl_pin_b, pin_side, is_replica_cell, cell_row=0, offset_x_vec=None): - """Connects the word lines together for a single bitcell. Also requires which side of the bitcell to short the pins.""" - #Assumes input pins are wordlines. Also assumes the word lines are horizontal in metal1. Also assumes pins have same x coord. - #This is my (Hunter) first time editing layout in openram so this function is likely not optimal. - if len(self.all_ports) > 1: - #1. Create vertical metal for all the bitlines to connect to - #m1 needs to be extended in the y directions, direction needs to be determined as every other cell is flipped - correct_y = vector(0, 0.5*drc("minwidth_metal1")) - #x spacing depends on the side being drawn. Unknown to me (Hunter) why the size of the space differs by the side. - #I assume this is related to how a wire is draw, but I have not investigated the issue. - if pin_side == "right": - correct_x = vector(0.5*drc("minwidth_metal1"), 0) - if offset_x_vec != None: - correct_x = offset_x_vec - else: - correct_x = vector(1.5*drc("minwidth_metal1"), 0) - - if wl_pin_a.uy() > wl_pin_b.uy(): - self.add_path("metal1", [wl_pin_a.rc()+correct_x+correct_y, wl_pin_b.rc()+correct_x-correct_y]) - else: - self.add_path("metal1", [wl_pin_a.rc()+correct_x-correct_y, wl_pin_b.rc()+correct_x+correct_y]) - elif pin_side == "left": - if offset_x_vec != None: - correct_x = offset_x_vec - else: - correct_x = vector(1.5*drc("minwidth_metal1"), 0) - - if wl_pin_a.uy() > wl_pin_b.uy(): - self.add_path("metal1", [wl_pin_a.lc()-correct_x+correct_y, wl_pin_b.lc()-correct_x-correct_y]) - else: - self.add_path("metal1", [wl_pin_a.lc()-correct_x-correct_y, wl_pin_b.lc()-correct_x+correct_y]) - else: - debug.error("Could not connect wordlines on specified input side={}".format(pin_side),1) - - #2. Connect word lines horizontally. Only replica cell needs. Bitline loads currently already do this. - for port in self.all_ports: - if is_replica_cell: - wl = self.wl_list[port] - pin = self.replica_cell_inst.get_pin(wl) - else: - wl = self.wl_list[port]+"_{}".format(cell_row) - pin = self.replica_column_inst.get_pin(wl) - - if pin_side == "left": - self.add_path("metal1", [pin.lc()-correct_x, pin.lc()]) - elif pin_side == "right": - self.add_path("metal1", [pin.rc()+correct_x, pin.rc()]) - - - - def route_supplies(self): - """ Propagate all vdd/gnd pins up to this level for all modules """ - - # These are the instances that every bank has - top_instances = [self.replica_column_inst, - self.delay_chain_inst] - for inst in top_instances: - self.copy_layout_pin(inst, "vdd") - self.copy_layout_pin(inst, "gnd") - - - # Route the inverter supply pin from M1 - # Only vdd is needed because gnd shares a rail with the delay chain - pin = self.rbl_inv_inst.get_pin("vdd") - self.add_power_pin("vdd", pin.lc()) - - for pin in self.replica_cell_inst.get_pins("vdd"): - self.add_power_pin(name="vdd", loc=pin.center(), vertical=True, start_layer=pin.layer) - - for pin in self.replica_cell_inst.get_pins("gnd"): - self.add_power_pin("gnd", pin.center(), vertical=True, start_layer=pin.layer) - - - - def route_access_tx(self): - # GATE ROUTE - # 1. Add the poly contact and nwell enclosure - # Determines the y-coordinate of where to place the gate input poly pin - # (middle in between the pmos and nmos) - - poly_pin = self.tx_inst.get_pin("G") - poly_offset = poly_pin.uc() - # This centers the contact above the poly by one pitch - contact_offset = poly_offset + vector(0,self.m2_pitch) - self.add_via_center(layers=("poly", "contact", "metal1"), - offset=contact_offset) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=contact_offset) - self.add_segment_center(layer="poly", - start=poly_offset, - end=contact_offset) - nwell_offset = self.rbl_inv_offset + vector(-self.inv.height,self.inv.width) - # self.add_rect(layer="nwell", - # offset=nwell_offset, - # width=0.5*self.inv.height, - # height=self.delay_chain_offset.y-nwell_offset.y) - - # 2. Route delay chain output to access tx gate - delay_en_offset = self.delay_chain_inst.get_pin("out").bc() - self.add_path("metal2", [delay_en_offset,contact_offset]) - - # 3. Route the contact of previous route to the bitcell WL - # route bend of previous net to bitcell WL - wl_offset = self.replica_cell_inst.get_pin(self.wl_list[0]).lc() - wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0) - wl_mid2 = vector(wl_mid1.x, contact_offset.y) - #xmid_point= 0.5*(wl_offset.x+contact_offset.x) - #wl_mid1 = vector(xmid_point,contact_offset.y) - #wl_mid2 = vector(xmid_point,wl_offset.y) - self.add_path("metal1", [wl_offset, wl_mid1, wl_mid2, contact_offset]) - - # 4. Short wodlines if multiport - wl = self.wl_list[0] - wl_last = self.wl_list[-1] - pin = self.replica_cell_inst.get_pin(wl) - pin_last = self.replica_cell_inst.get_pin(wl_last) - x_offset = self.short_wordlines(pin, pin_last, "left", True) - - #correct = vector(0.5*drc("minwidth_metal1"), 0) - #self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct]) - - # DRAIN ROUTE - # Route the drain to the vdd rail - drain_offset = self.tx_inst.get_pin("D").center() - self.add_power_pin("vdd", drain_offset, vertical=True) - - # SOURCE ROUTE - # Route the drain to the RBL inverter input - source_offset = self.tx_inst.get_pin("S").center() - inv_A_offset = self.rbl_inv_inst.get_pin("A").center() - self.add_path("metal1",[source_offset, inv_A_offset]) - - # Route the connection of the source route to the RBL bitline (left) - # Via will go halfway down from the bitcell - bl_offset = self.replica_cell_inst.get_pin(self.bl_list[0]).bc() - # Route down a pitch so we can use M2 routing - bl_down_offset = bl_offset - vector(0, self.m2_pitch) - self.add_path("metal2",[source_offset, bl_down_offset, bl_offset]) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=source_offset) - - # BODY ROUTE - # Connect it to the inverter well - nwell_offset = self.rbl_inv_inst.lr() - ur_offset = self.tx_inst.ur() - self.add_rect(layer="nwell", - offset=nwell_offset, - width=ur_offset.x-nwell_offset.x, - height=ur_offset.y-nwell_offset.y) - - def route_vdd(self): - """ Route all signals connected to vdd """ - - self.copy_layout_pin(self.delay_chain_inst,"vdd") - self.copy_layout_pin(self.replica_cell_inst,"vdd") - - # Connect the WL and vdd pins directly to the center and right vdd rails - # Connect RBL vdd pins to center and right rails - rbl_vdd_pins = self.replica_column_inst.get_pins("vdd") - for pin in rbl_vdd_pins: - if pin.layer != "metal1": - continue - start = vector(self.center_vdd_pin.cx(),pin.cy()) - end = vector(self.right_vdd_pin.cx(),pin.cy()) - self.add_layout_pin_segment_center(text="vdd", - layer="metal1", - start=start, - end=end) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=start) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=end) - - # Add via for the inverter - pin = self.rbl_inv_inst.get_pin("vdd") - start = vector(self.left_vdd_pin.cx(),pin.cy()) - end = vector(self.center_vdd_pin.cx(),pin.cy()) - self.add_segment_center(layer="metal1", - start=start, - end=end) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=start) - - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=end) - - - # Add via for the RBC - pin = self.replica_cell_inst.get_pin("vdd") - start = pin.lc() - end = vector(self.right_vdd_pin.cx(),pin.cy()) - self.add_segment_center(layer="metal1", - start=start, - end=end) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=end) - - # Create the RBL rails too - rbl_pins = self.replica_column_inst.get_pins("vdd") - for pin in rbl_pins: - if pin.layer != "metal1": - continue - # If above the delay line, route the full width - left = vector(self.left_vdd_pin.cx(),pin.cy()) - center = vector(self.center_vdd_pin.cx(),pin.cy()) - if pin.cy() > self.delay_chain_inst.uy() + self.m1_pitch: - start = left - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=left) - else: - start = center - end = vector(self.right_vdd_pin.cx()+0.5*self.m1_width,pin.cy()) - self.add_layout_pin_segment_center(text="vdd", - layer="metal1", - start=start, - end=end) - - - - - - - def route_gnd(self): - """ Route all signals connected to gnd """ - - # Route the gnd lines from left to right - - # Add via for the delay chain - left_gnd_start = self.delay_chain_inst.ll().scale(1,0) - vector(2*self.m2_pitch,0) - left_gnd_end = vector(left_gnd_start.x, self.replica_column_inst.uy()+self.m2_pitch) - self.left_gnd_pin=self.add_segment_center(layer="metal2", - start=left_gnd_start, - end=left_gnd_end) - - # Gnd line to the left of the replica bitline - center_gnd_start = self.replica_cell_inst.ll().scale(1,0) - vector(2*self.m2_pitch,0) - center_gnd_end = vector(center_gnd_start.x, self.replica_column_inst.uy()+self.m2_pitch) - self.center_gnd_pin=self.add_segment_center(layer="metal2", - start=center_gnd_start, - end=center_gnd_end) - - # Gnd line to the right of the replica bitline - right_gnd_start = self.replica_cell_inst.lr().scale(1,0) + vector(self.m2_pitch,0) - right_gnd_end = vector(right_gnd_start.x, self.replica_column_inst.uy()+self.m2_pitch) - self.right_gnd_pin=self.add_segment_center(layer="metal2", - start=right_gnd_start, - end=right_gnd_end) - - - - # Connect the WL and gnd pins directly to the center and right gnd rails - for row in range(self.bitcell_loads): - wl = self.wl_list[0]+"_{}".format(row) - pin = self.replica_column_inst.get_pin(wl) - if pin.layer != "metal1": - continue - # If above the delay line, route the full width - left = vector(self.left_gnd_pin.cx(),pin.cy()) - center = vector(self.center_gnd_pin.cx(),pin.cy()) - if pin.cy() > self.delay_chain_inst.uy() + self.m1_pitch: - start = left - else: - start = center - end = vector(self.right_gnd_pin.cx(),pin.cy()) - self.add_layout_pin_segment_center(text="gnd", - layer="metal1", - start=start, - end=end) - if start == left: - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=left) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=center) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=end) - - - # rbl_gnd_pins = self.replica_column_inst.get_pins("gnd") - # # Add L shapes to each vertical gnd rail - # for pin in rbl_gnd_pins: - # if pin.layer != "metal1": - # continue - # # If above the delay line, route the full width - # left = vector(self.left_gnd_pin.cx(),pin.cy()) - # center = vector(self.center_gnd_pin.cx(),pin.cy()) - # if pin.cy() > self.delay_chain_inst.uy() + self.m1_pitch: - # start = left - # else: - # start = center - # end = vector(self.right_gnd_pin.cx(),pin.cy()) - # self.add_segment_center(layer="metal1", - # start=start, - # end=end) - # if start == left: - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=left) - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=center) - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=end) - - - - # Connect the gnd pins of the delay chain to the left rails - dc_gnd_pins = self.delay_chain_inst.get_pins("gnd") - for pin in dc_gnd_pins: - if pin.layer != "metal1": - continue - start = vector(self.left_gnd_pin.cx(),pin.cy()) - # Note, we don't connect to the center rails because of - # via conflicts with the RBL - #end = vector(self.center_gnd_pin.cx(),pin.cy()) - end = pin.rc() - self.add_segment_center(layer="metal1", - start=start, - end=end) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=start) - - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=end) - - - # Add via for the inverter - # pin = self.rbl_inv_inst.get_pin("gnd") - # start = vector(self.left_gnd_pin.cx(),pin.cy()) - # end = vector(self.center_gnd_pin.cx(),pin.cy()) - # self.add_segment_center(layer="metal1", - # start=start, - # end=end) - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=start) - # self.add_via_center(layers=("metal1", "via1", "metal2"), - # offset=end) - - - - # Create RBL rails too - rbl_pins = self.replica_column_inst.get_pins("gnd") - for pin in rbl_pins: - if pin.layer != "metal2": - continue - start = vector(pin.cx(),self.right_gnd_pin.by()) - end = vector(pin.cx(),self.right_gnd_pin.uy()) - self.add_layout_pin_segment_center(text="gnd", - layer="metal2", - start=start, - end=end) - - - - def add_layout_pins(self): - """ Route the input and output signal """ - en_offset = self.delay_chain_inst.get_pin("in").bc() - self.add_layout_pin_segment_center(text="en", - layer="metal2", - start=en_offset, - end=en_offset.scale(1,0)) - - out_offset = self.rbl_inv_inst.get_pin("Z").center() - self.add_layout_pin_segment_center(text="out", - layer="metal2", - start=out_offset, - end=out_offset.scale(1,0)) - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=out_offset) - - 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.rbl_inv_inst.get_pin("A") - self.add_label_pin(text="bl[0]", - layer=pin.layer, - offset=pin.ll(), - height=pin.height(), - width=pin.width()) - - pin = self.delay_chain_inst.get_pin("out") - self.add_label_pin(text="delayed_en", - layer=pin.layer, - offset=pin.ll(), - height=pin.height(), - width=pin.width()) - - def get_en_cin(self): - """Get the enable input relative capacitance""" - #The enable is only connected to the delay, get the cin from that module - en_cin = self.delay_chain.get_cin() - return en_cin - - def determine_sen_stage_efforts(self, ext_cout, inp_is_rise=True): - """Get the stage efforts from the en to s_en. Does not compute the delay for the bitline load.""" - stage_effort_list = [] - #Stage 1 is the delay chain - stage1_cout = self.get_delayed_en_cin() - stage1 = self.delay_chain.determine_delayed_en_stage_efforts(stage1_cout, inp_is_rise) - stage_effort_list += stage1 - - #There is a disconnect between the delay chain and inverter. The rise/fall of the input to the inverter - #Will be the negation of the previous stage. - last_stage_is_rise = not stage_effort_list[-1].is_rise - - #The delay chain triggers the enable on the replica bitline (rbl). This is used to track the bitline delay whereas this - #model is intended to track every but that. Therefore, the next stage is the inverter after the rbl. - stage2 = self.inv.get_stage_effort(ext_cout, last_stage_is_rise) - stage_effort_list.append(stage2) - - return stage_effort_list - - def get_delayed_en_cin(self): - """Get the fanout capacitance (relative) of the delayed enable from the delay chain.""" - access_tx_cin = self.access_tx.get_cin() - rbc_cin = self.replica_bitcell.get_wl_cin() - return access_tx_cin + rbc_cin - diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index 0de0aace..c3f63b19 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -14,12 +14,25 @@ from globals import OPTS class replica_column(design.design): """ Generate a replica bitline column for the replica array. + Rows is the total number of rows i the main array. + Left_rbl and right_rbl are the number of left and right replica bitlines. + Replica bit specifies which replica column this is (to determine where to put the + replica cell. """ - def __init__(self, name, rows): + def __init__(self, name, rows, left_rbl, right_rbl, replica_bit): design.design.__init__(self, name) - self.row_size = rows + self.rows = rows + self.left_rbl = left_rbl + self.right_rbl = right_rbl + self.replica_bit = replica_bit + # left, right, regular rows plus top/bottom dummy cells + self.total_size = self.left_rbl+rows+self.right_rbl+2 + + debug.check(replica_bit!=0 and replica_bit!=rows,"Replica bit cannot be the dummy row.") + debug.check(replica_bit<=left_rbl or replica_bit>=self.total_size-right_rbl-1, + "Replica bit cannot be in the regular array.") self.create_netlist() if not OPTS.netlist_only: @@ -31,26 +44,26 @@ class replica_column(design.design): self.create_instances() def create_layout(self): - self.place_instances() - self.add_layout_pins() - - self.height = self.row_size*self.cell.height + self.height = self.total_size*self.cell.height self.width = self.cell.width + self.place_instances() + self.add_layout_pins() self.add_boundary() self.DRC_LVS() def add_pins(self): - column_list = self.cell.list_all_bitline_names() - for cell_column in column_list: - self.add_pin("{0}_{1}".format(cell_column,0)) - row_list = self.cell.list_all_wl_names() - for row in range(self.row_size): - for cell_row in row_list: - self.add_pin("{0}_{1}".format(cell_row,row)) + + for bl_name in self.cell.get_all_bitline_names(): + # In the replica column, these are only outputs! + self.add_pin("{0}_{1}".format(bl_name,0), "OUTPUT") + + for row in range(self.total_size): + for wl_name in self.cell.get_all_wl_names(): + self.add_pin("{0}_{1}".format(wl_name,row), "INPUT") - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") def add_modules(self): self.replica_cell = factory.create(module_type="replica_bitcell") @@ -62,76 +75,77 @@ class replica_column(design.design): def create_instances(self): self.cell_inst = {} - for row in range(self.row_size): + for row in range(self.total_size): name="rbc_{0}".format(row) - if row>0 and rowleft_rbl and self.left_rbl and row ... -> INVn""" + """ Calculate the analytical delay of INV1 -> ... -> INVn """ cout_list = [] for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]): @@ -198,9 +198,12 @@ class pdriver(pgate.pgate): return delay - + def get_sizes(self): + """ Return the relative sizes of the buffers """ + return self.size_list + def get_stage_efforts(self, external_cout, inp_is_rise=False): - """Get the stage efforts of the A -> Z path""" + """ Get the stage efforts of the A -> Z path """ cout_list = [] for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]): cout_list.append(inv.get_cin()) @@ -217,5 +220,5 @@ class pdriver(pgate.pgate): return stage_effort_list def get_cin(self): - """Returns the relative capacitance of the input""" + """ Returns the relative capacitance of the input """ return self.inv_list[0].get_cin() diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 906a009d..773a0452 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -60,7 +60,7 @@ class pinv(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ pin_list = ["A", "Z", "vdd", "gnd"] - dir_list = ['INPUT', 'OUTPUT', 'POWER', 'GROUND'] + dir_list = ["INPUT", "OUTPUT", "POWER", "GROUND"] self.add_pin_list(pin_list, dir_list) @@ -300,4 +300,4 @@ class pinv(pgate.pgate): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 6e3fb7ae..956f0a30 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -61,7 +61,7 @@ class pnand2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ pin_list = ["A", "B", "Z", "vdd", "gnd"] - dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + dir_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] self.add_pin_list(pin_list, dir_list) @@ -281,4 +281,4 @@ class pnand2(pgate.pgate): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index a8cce176..30da9165 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -44,7 +44,7 @@ class pnand3(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ pin_list = ["A", "B", "C", "Z", "vdd", "gnd"] - dir_list = ['INPUT', 'INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + dir_list = ["INPUT", "INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] self.add_pin_list(pin_list, dir_list) def create_netlist(self): @@ -283,4 +283,4 @@ class pnand3(pgate.pgate): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 7f026da3..fa52c528 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -41,7 +41,7 @@ class pnor2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ pin_list = ["A", "B", "Z", "vdd", "gnd"] - dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'INOUT', 'INOUT'] + dir_list = ["INPUT", "INPUT", "OUTPUT", "INOUT", "INOUT"] self.add_pin_list(pin_list, dir_list) def create_netlist(self): @@ -242,4 +242,4 @@ class pnor2(pgate.pgate): def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) \ No newline at end of file + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index 4e37aeea..b4423bed 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -53,7 +53,7 @@ class precharge(design.design): self.connect_to_bitlines() def add_pins(self): - self.add_pin_list(["bl", "br", "en_bar", "vdd"]) + self.add_pin_list(["bl", "br", "en_bar", "vdd"], ["OUTPUT", "OUTPUT", "INPUT", "POWER"]) def add_ptx(self): """ diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 0ee0bb4f..355ca769 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -99,13 +99,13 @@ class sram_1bank(sram_base): # This includes 2 M2 pitches for the row addr clock line. control_pos[port] = vector(-self.control_logic_insts[port].width - 2*self.m2_pitch, - self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - self.bank.m2_gap) + self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2*self.bank.m2_gap ) self.control_logic_insts[port].place(control_pos[port]) # The row address bits are placed above the control logic aligned on the right. x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is aove the control logic but below the top of the bitcell array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.bank_array_ur.y - self.row_addr_dff_insts[port].height) + # It is above the control logic but below the top of the bitcell array + y_offset = max(self.control_logic_insts[port].uy(), self.bank_inst.uy() - self.row_addr_dff_insts[port].height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port]) @@ -166,16 +166,35 @@ class sram_1bank(sram_base): # This includes 2 M2 pitches for the row addr clock line control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2*self.m2_pitch, - self.bank.bank_array_ur.y + self.control_logic_insts[port].mod.control_logic_center.y + self.bank.m2_gap) + self.bank.bank_array_ur.y + self.control_logic_insts[port].height - + (self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y) + + 2*self.bank.m2_gap) + #import pdb; pdb.set_trace() self.control_logic_insts[port].place(control_pos[port], mirror="XY") # The row address bits are placed above the control logic aligned on the left. x_offset = control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width - # It is above the control logic but below the top of the bitcell array - y_offset = min(self.control_logic_insts[port].by(), self.bank.bank_array_ll.y - self.row_addr_dff_insts[port].height) + # It is below the control logic but below the bottom of the bitcell array + y_offset = min(self.control_logic_insts[port].by(), self.bank_inst.by() + self.row_addr_dff_insts[port].height) row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY") + # Add the data flops above the bank to the left of the upper-right of bank array + # This relies on the upper-right of the array of the bank + # decoder in upper left, bank in upper right, sensing in lower right. + # These flops go below the sensing and leave a gap to channel route to the + # sense amps. + if port in self.write_ports: + data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.height + max_gap_size + self.dff.height) + self.data_dff_insts[port].place(data_pos[port], mirror="MX") + + # Add the write mask flops to the left of the din flops. + if self.write_size is not None: + if port in self.write_ports: + wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width, + self.bank.height + max_gap_size + self.data_dff_insts[port].height) + self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX") def add_layout_pins(self): @@ -271,7 +290,8 @@ class sram_1bank(sram_base): def route_control_logic(self): - """ Route the outputs from the control logic module """ + """ Route the control logic pins that are not inputs """ + for port in self.all_ports: for signal in self.control_logic_outputs[port]: # The clock gets routed separately and is not a part of the bank @@ -279,10 +299,14 @@ class sram_1bank(sram_base): continue src_pin = self.control_logic_insts[port].get_pin(signal) dest_pin = self.bank_inst.get_pin(signal+"{}".format(port)) - self.connect_rail_from_left_m2m3(src_pin, dest_pin) - self.add_via_center(layers=("metal1","via1","metal2"), - offset=src_pin.rc()) - + self.connect_vbus_m2m3(src_pin, dest_pin) + + for port in self.read_ports: + # Only input (besides pins) is the replica bitline + src_pin = self.control_logic_insts[port].get_pin("rbl_bl") + dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port)) + self.connect_vbus_m2m3(src_pin, dest_pin) + def route_row_addr_dff(self): """ Connect the output of the row flops to the bank pins """ @@ -401,4 +425,4 @@ class sram_1bank(sram_base): #Sanity check in case it was forgotten if inst_name.find('x') != 0: inst_name = 'x'+inst_name - return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col) \ No newline at end of file + return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col) diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 7b7a77f0..45befe16 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -120,7 +120,7 @@ class sram_base(design, verilog, lef): self.add_lvs_correspondence_points() - self.offset_all_coordinates() + #self.offset_all_coordinates() highest_coord = self.find_highest_coords() self.width = highest_coord[0] @@ -197,7 +197,7 @@ class sram_base(design, verilog, lef): if self.port_id[port] == "r": self.control_bus_names[port].extend([sen, pen]) elif self.port_id[port] == "w": - self.control_bus_names[port].extend([wen]) + self.control_bus_names[port].extend([wen, pen]) else: self.control_bus_names[port].extend([sen, wen, pen]) self.vert_control_bus_positions = self.create_vertical_bus(layer="metal2", @@ -301,9 +301,6 @@ class sram_base(design, verilog, lef): self.bank_count = 0 - self.supply_rail_width = self.bank.supply_rail_width - self.supply_rail_pitch = self.bank.supply_rail_pitch - #The control logic can resize itself based on the other modules. Requires all other modules added before control logic. self.all_mods_except_control_done = True @@ -342,6 +339,10 @@ class sram_base(design, verilog, lef): for port in self.read_ports: for bit in range(self.word_size): temp.append("DOUT{0}[{1}]".format(port,bit)) + for port in self.read_ports: + temp.append("rbl_bl{0}".format(port)) + for port in self.read_ports: + temp.append("rbl_wl{0}".format(port)) for port in self.write_ports: for bit in range(self.word_size): temp.append("BANK_DIN{0}[{1}]".format(port,bit)) @@ -353,7 +354,7 @@ class sram_base(design, verilog, lef): temp.append("bank_sel{0}[{1}]".format(port,bank_num)) for port in self.read_ports: temp.append("s_en{0}".format(port)) - for port in self.read_ports: + for port in self.all_ports: temp.append("p_en_bar{0}".format(port)) for port in self.write_ports: temp.append("w_en{0}".format(port)) @@ -501,41 +502,46 @@ class sram_base(design, verilog, lef): temp.append("web{}".format(port)) temp.append("clk{}".format(port)) - # for port in self.all_ports: - # self.add_pin("csb{}".format(port), "INPUT") - # for port in self.readwrite_ports: - # self.add_pin("web{}".format(port), "INPUT") - # for port in self.all_ports: - # self.add_pin("clk{}".format(port), "INPUT") + if port in self.read_ports: + temp.append("rbl_bl{}".format(port)) + + # Ouputs + if port in self.read_ports: + temp.append("rbl_wl{}".format(port)) - # Outputs if port in self.read_ports: temp.append("s_en{}".format(port)) if port in self.write_ports: temp.append("w_en{}".format(port)) - if port in self.read_ports: - temp.append("p_en_bar{}".format(port)) + temp.append("p_en_bar{}".format(port)) temp.extend(["wl_en{}".format(port), "clk_buf{}".format(port), "vdd", "gnd"]) self.connect_inst(temp) return insts - def connect_rail_from_left_m2m3(self, src_pin, dest_pin): - """ Helper routine to connect an unrotated/mirrored oriented instance to the rails """ - in_pos = src_pin.rc() + def connect_vbus_m2m3(self, src_pin, dest_pin): + """ Helper routine to connect an instance to a vertical bus. + Routes horizontal then vertical L shape. + Dest pin is assumed to be on M2. + Src pin can be on M1/M2/M3.""" + + if src_pin.cx()> rect -3 101 37 138 rect -3 0 37 51 @@ -10,7 +10,6 @@ rect -3 51 37 101 << ntransistor >> rect 9 178 11 190 rect 17 178 19 190 -rect 15 163 27 165 rect 9 144 11 148 rect 17 144 19 148 rect 10 82 12 89 @@ -31,10 +30,6 @@ rect 8 178 9 190 rect 11 178 12 190 rect 16 178 17 190 rect 19 178 20 190 -rect 15 165 27 166 -rect 15 162 27 163 -rect 12 158 15 161 -rect 12 156 16 158 rect 8 144 9 148 rect 11 144 12 148 rect 16 144 17 148 @@ -71,8 +66,6 @@ rect 3 35 7 38 rect 4 178 8 190 rect 12 178 16 190 rect 20 178 24 190 -rect 15 166 27 170 -rect 15 158 27 162 rect 4 144 8 148 rect 12 144 16 148 rect 20 144 24 148 @@ -95,7 +88,6 @@ rect 11 38 15 45 rect 19 38 23 45 rect 27 38 31 45 << psubstratepcontact >> -rect 12 152 16 156 rect 26 82 30 89 << nsubstratencontact >> rect 12 118 16 122 @@ -109,8 +101,6 @@ rect 9 176 11 178 rect 17 173 19 178 rect 6 171 19 173 rect 6 168 8 171 -rect 13 163 15 165 -rect 27 163 33 165 rect 9 148 11 150 rect 17 148 19 150 rect 9 132 11 144 @@ -133,12 +123,9 @@ rect 18 89 20 90 rect 10 81 12 82 rect 10 79 13 81 rect 2 71 3 75 -rect 11 71 13 79 +rect 11 67 13 79 rect 18 79 20 82 rect 18 77 23 79 -rect 31 71 33 163 -rect 11 69 33 71 -rect 11 67 13 69 rect 8 65 13 67 rect 8 64 10 65 rect 16 64 18 66 @@ -164,13 +151,9 @@ rect 15 10 19 14 rect 5 193 10 197 rect 5 190 8 193 rect 32 182 33 186 -rect 13 170 16 178 -rect 13 166 15 170 rect 4 148 8 164 -rect 12 158 15 162 -rect 12 156 16 158 -rect 23 157 27 158 -rect 12 148 16 152 +rect 12 163 16 178 +rect 12 148 16 159 rect 4 132 8 144 rect 20 142 24 144 rect 30 142 33 182 @@ -199,7 +182,7 @@ rect 11 24 36 28 << m2contact >> rect 10 193 14 197 rect 20 190 24 194 -rect 23 153 27 157 +rect 12 159 16 163 rect 16 118 20 122 rect 26 89 30 90 rect 26 86 30 89 @@ -220,7 +203,7 @@ rlabel m2contact 21 66 21 66 1 gnd rlabel m2contact 28 88 28 88 1 gnd rlabel m2contact 21 33 21 33 1 vdd rlabel m2contact 18 120 18 120 1 vdd -rlabel m2contact 25 155 25 155 1 gnd rlabel metal2 12 201 12 201 5 bl rlabel metal2 22 201 22 201 5 br +rlabel m2contact 14 161 14 161 1 gnd << end >> diff --git a/technology/scn4m_subm/sp_lib/write_driver.sp b/technology/scn4m_subm/sp_lib/write_driver.sp index d1dbf9b2..e86da288 100644 --- a/technology/scn4m_subm/sp_lib/write_driver.sp +++ b/technology/scn4m_subm/sp_lib/write_driver.sp @@ -28,9 +28,8 @@ M_14 din_gated_bar din_gated gnd gnd n W=0.8u L=0.4u ************************************************ * pull down with en enable -M_15 bl din_gated_bar net_5 gnd n W=2.4u L=0.4u -M_16 br din_bar_gated_bar net_5 gnd n W=2.4u L=0.4u -M_17 net_5 en gnd gnd n W=2.4u L=0.4u +M_15 bl din_gated_bar gnd gnd n W=2.4u L=0.4u +M_16 br din_bar_gated_bar gnd gnd n W=2.4u L=0.4u