diff --git a/compiler/base/design.py b/compiler/base/design.py index 55ff81e2..da23aa9e 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -98,8 +98,8 @@ class design(hierarchy_design): for port in range(OPTS.num_r_ports): self.read_ports.append(port_number) self.readonly_ports.append(port_number) - port_number += 1 - + port_number += 1 + def analytical_power(self, corner, load): """ Get total power of a module """ total_module_power = self.return_power() diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py new file mode 100644 index 00000000..b6ac4677 --- /dev/null +++ b/compiler/base/graph_util.py @@ -0,0 +1,86 @@ +import os, copy +from collections import defaultdict + +import gdsMill +import tech +import math +import globals +import debug +from vector import vector +from pin_layout import pin_layout + +class timing_graph(): + """Implements a directed graph + Nodes are currently just Strings. + """ + + def __init__(self): + self.graph = defaultdict(set) + self.all_paths = [] + + 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): + """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. + self.remove_edges('vdd') + self.remove_edges('gnd') + + # Mark all the vertices as not visited + visited = set() + + # Create an array to store paths + path = [] + self.all_paths = [] + + # 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))) + + return self.all_paths + + 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) + + # If current vertex is same as destination, then print + # current path[] + if cur_node == dest_node: + self.all_paths.append(copy.deepcopy(path)) + else: + # If current vertex is not destination + #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) + + # Remove current vertex from path[] and mark it as unvisited + path.pop() + visited.remove(cur_node) + + def __str__(self): + """ override print function output """ + return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) \ No newline at end of file diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 66ecba9c..05b2182f 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -12,6 +12,7 @@ import verify import debug import os from globals import OPTS +import graph_util total_drc_errors = 0 total_lvs_errors = 0 @@ -30,7 +31,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): self.name = name hierarchy_spice.spice.__init__(self, name) hierarchy_layout.layout.__init__(self, name) - + self.init_graph_params() def get_layout_pins(self,inst): """ Return a map of pin locations of the instance offset """ @@ -103,8 +104,123 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): total_lvs_errors += num_errors debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors)) os.remove(tempspice) - os.remove(tempgds) + os.remove(tempgds) + + def init_graph_params(self): + """Initializes parameters relevant to the graph creation""" + #Only initializes a set for checking instances which should not be added + self.graph_inst_exclude = set() + + def build_graph(self, graph, inst_name, port_nets): + """Recursively create graph from instances in module.""" + + #Translate port names to external nets + if len(port_nets) != len(self.pins): + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) + port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + debug.info(3, "Instance name={}".format(inst_name)) + for subinst, conns in zip(self.insts, self.conns): + if subinst in self.graph_inst_exclude: + continue + subinst_name = inst_name+'.X'+subinst.name + subinst_ports = self.translate_nets(conns, port_dict, inst_name) + subinst.mod.build_graph(graph, subinst_name, subinst_ports) + + def build_names(self, name_dict, inst_name, port_nets): + """Collects all the nets and the parent inst of that net.""" + #Translate port names to external nets + if len(port_nets) != len(self.pins): + debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1) + port_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + debug.info(3, "Instance name={}".format(inst_name)) + for subinst, conns in zip(self.insts, self.conns): + subinst_name = inst_name+'.X'+subinst.name + subinst_ports = self.translate_nets(conns, port_dict, inst_name) + for si_port, conn in zip(subinst_ports, conns): + #Only add for first occurrence + if si_port.lower() not in name_dict: + mod_info = {'mod':self, 'int_net':conn} + name_dict[si_port.lower()] = mod_info + subinst.mod.build_names(name_dict, subinst_name, subinst_ports) + def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None): + """Given a list of nets, will compare the internal alias of a mod to determine + if the nets have a connection to this mod's net (but not inst). + """ + if exclusion_set == None: + exclusion_set = set() + try: + self.name_dict + except AttributeError: + self.name_dict = {} + self.build_names(self.name_dict, inst_name, port_nets) + aliases = [] + for net in path_nets: + net = net.lower() + int_net = self.name_dict[net]['int_net'] + int_mod = self.name_dict[net]['mod'] + if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set): + aliases.append(net) + return aliases + + def is_net_alias(self, known_net, net_alias, mod, exclusion_set): + """Checks if the alias_net in input mod is the same as the input net for this mod (self).""" + if self in exclusion_set: + return False + #Check ports of this mod + for pin in self.pins: + if self.is_net_alias_name_check(known_net, pin, net_alias, mod): + return True + #Check connections of all other subinsts + mod_set = set() + for subinst, inst_conns in zip(self.insts, self.conns): + for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins): + if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod): + return True + elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set: + if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set): + return True + mod_set.add(subinst.mod) + return False + + def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod): + """Utility function for checking single net alias.""" + return self == mod and \ + child_net.lower() == alias_net.lower() and \ + parent_net.lower() == alias_net.lower() + + def get_mod_net(self, parent_net, child_inst, child_conns): + """Given an instance and net, returns the internal net in the mod + corresponding to input net.""" + for conn, pin in zip(child_conns, child_inst.mod.pins): + if parent_net.lower() == conn.lower(): + return pin + return None + + def translate_nets(self, subinst_ports, port_dict, inst_name): + """Converts connection names to their spice hierarchy equivalent""" + converted_conns = [] + for conn in subinst_ports: + if conn in port_dict: + converted_conns.append(port_dict[conn]) + else: + converted_conns.append("{}.{}".format(inst_name, conn)) + return converted_conns + + def add_graph_edges(self, graph, port_nets): + """For every input, adds an edge to every output. + Only intended to be used for gates and other simple modules.""" + #The final pin names will depend on the spice hierarchy, so + #they are passed as an input. + pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + input_pins = self.get_inputs() + output_pins = self.get_outputs() + inout_pins = self.get_inouts() + for inp in input_pins+inout_pins: + for out in output_pins+inout_pins: + if inp != out: #do not add self loops + graph.add_edge(pin_dict[inp], pin_dict[out]) + def __str__(self): """ override print function output """ pins = ",".join(self.pins) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 0d649598..f4fc9d8a 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -73,6 +73,17 @@ class spice(): else: debug.error("Mismatch in type and pin list lengths.", -1) + def add_pin_types(self, type_list): + """Add pin types for all the cell's pins. + Typically, should only be used for handmade cells.""" + #This only works if self.pins == bitcell.pin_names + if self.pin_names != self.pins: + debug.error("{} spice subcircuit port names do not match pin_names\ + \n SPICE names={}\ + \n Module names={}\ + ".format(self.name, self.pin_names, self.pins),1) + self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)} + def get_pin_type(self, name): """ Returns the type of the signal pin. """ return self.pin_type[name] @@ -102,6 +113,14 @@ class spice(): output_list.append(pin) return output_list + def get_inouts(self): + """ These use pin types to determine pin lists. These + may be over-ridden by submodules that didn't use pin directions yet.""" + inout_list = [] + for pin in self.pins: + if self.pin_type[pin]=="INOUT": + inout_list.append(pin) + return inout_list def add_mod(self, mod): """Adds a subckt/submodule to the subckt hierarchy""" @@ -136,7 +155,13 @@ class spice(): debug.error("-----") debug.error("Connections: \n"+str(conns_string),1) - + def get_conns(self, inst): + """Returns the connections of a given instance.""" + for i in range(len(self.insts)): + if inst is self.insts[i]: + return self.conns[i] + #If not found, returns None + return None def sp_read(self): """Reads the sp file (and parse the pins) from the library @@ -157,6 +182,28 @@ class spice(): else: self.spice = [] + def check_net_in_spice(self, net_name): + """Checks if a net name exists in the current. Intended to be check nets in hand-made cells.""" + #Remove spaces and lower case then add spaces. Nets are separated by spaces. + net_formatted = ' '+net_name.lstrip().rstrip().lower()+' ' + for line in self.spice: + #Lowercase the line and remove any part of the line that is a comment. + line = line.lower().split('*')[0] + + #Skip .subckt or .ENDS lines + if line.find('.') == 0: + continue + if net_formatted in line: + return True + return False + + def do_nets_exist(self, nets): + """For handmade cell, checks sp file contains the storage nodes.""" + nets_match = True + for net in nets: + nets_match = nets_match and self.check_net_in_spice(net) + return nets_match + def contains(self, mod, modlist): for x in modlist: if x.name == mod.name: diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 9711de62..7a4ab765 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -20,6 +20,8 @@ class bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + storage_nets = ['Q', 'Qbar'] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) @@ -31,7 +33,9 @@ class bitcell(design.design): self.width = bitcell.width self.height = bitcell.height self.pin_map = bitcell.pin_map - + self.add_pin_types(self.type_list) + self.nets_match = self.do_nets_exist(self.storage_nets) + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node @@ -67,6 +71,14 @@ class bitcell(design.design): column_pins = ["br"] return column_pins + def get_bl_name(self): + """Get bl name""" + return "bl" + + def get_br_name(self): + """Get bl name""" + return "br" + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -74,10 +86,23 @@ class bitcell(design.design): dynamic = 0 #temporary total_power = self.return_power(dynamic, leakage) return total_power - + + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) + return None + 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. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + 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 diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index b6519cd2..bd72c61a 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -20,6 +20,8 @@ class bitcell_1rw_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Q_bar'] (width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"]) @@ -31,7 +33,9 @@ class bitcell_1rw_1r(design.design): self.width = bitcell_1rw_1r.width self.height = bitcell_1rw_1r.height self.pin_map = bitcell_1rw_1r.pin_map - + self.add_pin_types(self.type_list) + self.nets_match = self.do_nets_exist(self.storage_nets) + def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 size = 0.5 #This accounts for bitline being drained thought the access TX and internal node @@ -91,6 +95,14 @@ class bitcell_1rw_1r(design.design): column_pins = ["br0"] return column_pins + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -106,3 +118,24 @@ class bitcell_1rw_1r(design.design): #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 get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) + return None + + 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.""" + 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 edges + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index b32fedc3..13669dcb 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -20,6 +20,8 @@ class bitcell_1w_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Q_bar'] (width,height) = utils.get_libcell_size("cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"]) @@ -31,6 +33,8 @@ class bitcell_1w_1r(design.design): self.width = bitcell_1w_1r.width self.height = bitcell_1w_1r.height self.pin_map = bitcell_1w_1r.pin_map + self.add_pin_types(self.type_list) + self.nets_match = self.do_nets_exist(self.storage_nets) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -91,6 +95,14 @@ class bitcell_1w_1r(design.design): column_pins = ["br0"] return column_pins + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) + def analytical_power(self, corner, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -106,3 +118,22 @@ class bitcell_1w_1r(design.design): #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 get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) + return None + + 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.""" + 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. diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 9db22930..eb44a27a 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -97,47 +97,49 @@ class pbitcell(design.design): port = 0 for k in range(self.num_rw_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "OUTPUT") + self.add_pin("br{}".format(port), "OUTPUT") self.rw_bl_names.append("bl{}".format(port)) self.rw_br_names.append("br{}".format(port)) port += 1 for k in range(self.num_w_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "INPUT") + self.add_pin("br{}".format(port), "INPUT") self.w_bl_names.append("bl{}".format(port)) self.w_br_names.append("br{}".format(port)) port += 1 for k in range(self.num_r_ports): - self.add_pin("bl{}".format(port)) - self.add_pin("br{}".format(port)) + self.add_pin("bl{}".format(port), "OUTPUT") + self.add_pin("br{}".format(port), "OUTPUT") self.r_bl_names.append("bl{}".format(port)) self.r_br_names.append("br{}".format(port)) port += 1 port = 0 for k in range(self.num_rw_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.rw_wl_names.append("wl{}".format(port)) port += 1 for k in range(self.num_w_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.w_wl_names.append("wl{}".format(port)) port += 1 for k in range(self.num_r_ports): - self.add_pin("wl{}".format(port)) + self.add_pin("wl{}".format(port), "INPUT") self.r_wl_names.append("wl{}".format(port)) port += 1 - self.add_pin("vdd") - self.add_pin("gnd") + self.add_pin("vdd", "POWER") + self.add_pin("gnd", "GROUND") # if this is a replica bitcell, replace the instances of Q_bar with vdd if self.replica_bitcell: self.Q_bar = "vdd" else: self.Q_bar = "Q_bar" - + self.Q = "Q" + self.storage_nets = [self.Q, self.Q_bar] + def add_modules(self): """ Determine size of transistors and add ptx modules """ # if there are any read/write ports, @@ -270,20 +272,20 @@ class pbitcell(design.design): # create active for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", mod=self.inverter_nmos) - self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"]) + self.connect_inst([self.Q, self.Q_bar, "gnd", "gnd"]) self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", mod=self.inverter_nmos) - self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"]) + self.connect_inst(["gnd", self.Q, self.Q_bar, "gnd"]) # create active for pmos self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", mod=self.inverter_pmos) - self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"]) + self.connect_inst([self.Q, self.Q_bar, "vdd", "vdd"]) self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", mod=self.inverter_pmos) - self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"]) + self.connect_inst(["vdd", self.Q, self.Q_bar, "vdd"]) def place_storage(self): """ Places the transistors for the crossed coupled inverters in the bitcell """ @@ -377,7 +379,7 @@ class pbitcell(design.design): # 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], "Q", "gnd"]) + self.connect_inst([self.rw_bl_names[k], 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) @@ -451,7 +453,7 @@ class pbitcell(design.design): # 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], "Q", "gnd"]) + self.connect_inst([self.w_bl_names[k], 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) @@ -537,7 +539,7 @@ class pbitcell(design.design): self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k), mod=self.read_nmos) - self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"]) + self.connect_inst(["gnd", self.Q, "RA_to_R_right{}".format(k), "gnd"]) # add read transistors self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), @@ -884,6 +886,18 @@ class pbitcell(design.design): Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() self.add_path("metal1", [Q_bar_pos, vdd_pos]) + + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + return self.storage_nets + + def get_bl_name(self, port=0): + """Get bl name by port""" + return "bl{}".format(port) + + def get_br_name(self, port=0): + """Get bl name by port""" + return "br{}".format(port) def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -911,3 +925,14 @@ class pbitcell(design.design): access_tx_cin = self.readwrite_nmos.get_cin() return 2*access_tx_cin + def build_graph(self, graph, inst_name, port_nets): + """Adds edges to graph for pbitcell. Only readwrite and read ports.""" + 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 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.py b/compiler/bitcells/replica_bitcell.py index f95249b0..1f0d5842 100644 --- a/compiler/bitcells/replica_bitcell.py +++ b/compiler/bitcells/replica_bitcell.py @@ -18,6 +18,7 @@ class replica_bitcell(design.design): the technology library. """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"]) @@ -29,6 +30,7 @@ class replica_bitcell(design.design): self.width = replica_bitcell.width self.height = replica_bitcell.height self.pin_map = replica_bitcell.pin_map + self.add_pin_types(self.type_list) def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" @@ -36,3 +38,7 @@ class replica_bitcell(design.design): #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] return 2*access_tx_cin + + 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 diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index 1d8e647e..e5c33317 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -18,6 +18,7 @@ class replica_bitcell_1rw_1r(design.design): 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("replica_cell_1rw_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"]) @@ -29,6 +30,7 @@ class replica_bitcell_1rw_1r(design.design): self.width = replica_bitcell_1rw_1r.width self.height = replica_bitcell_1rw_1r.height self.pin_map = replica_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""" @@ -37,3 +39,15 @@ class replica_bitcell_1rw_1r(design.design): #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): + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + 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 edges + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index 7eea462a..c0d31a69 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -18,6 +18,7 @@ class replica_bitcell_1w_1r(design.design): 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("replica_cell_1w_1r", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"]) @@ -29,6 +30,7 @@ class replica_bitcell_1w_1r(design.design): self.width = replica_bitcell_1w_1r.width self.height = replica_bitcell_1w_1r.height self.pin_map = replica_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""" @@ -37,3 +39,13 @@ class replica_bitcell_1w_1r(design.design): #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): + """Adds edges to graph. Multiport bitcell timing graph is too complex + to use the add_graph_edges function.""" + 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 diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 23efe86c..8153251b 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -15,7 +15,6 @@ from .setup_hold import * from .functional import * from .worst_case import * from .simulation import * -from .bitline_delay import * from .measurements import * from .model_check import * diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py deleted file mode 100644 index 68985b2a..00000000 --- a/compiler/characterizer/bitline_delay.py +++ /dev/null @@ -1,248 +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 bitline_delay(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) - self.period = tech.spice["feasible_period"] - self.is_bitline_measure = True - - def create_signal_names(self): - delay.create_signal_names(self) - self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"] - self.sen_name = "Xsram.s_en" - - 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. - self.bl_volt_meas_names = ["volt_bl", "volt_br"] - self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation - self.bl_delay_result_name = "delay_bl_vth" #Used in the return value - - 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.""" - delay.set_probe(self,probe_address, probe_data) - self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) - - def write_delay_measures(self): - """ - Write the measure statements to quantify the bitline voltage at sense amp enable 50%. - """ - self.sf.write("\n* Measure statements for delay and power\n") - - # Output some comments to aid where cycles start and - for comment in self.cycle_comments: - self.sf.write("* {}\n".format(comment)) - - for read_port in self.targ_read_ports: - self.write_bitline_voltage_measures(read_port) - self.write_bitline_delay_measures(read_port) - - def write_bitline_voltage_measures(self, port): - """ - Add measurments to capture the bitline voltages at 50% Sense amp enable - """ - debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port)) - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - sen_port_name = "{}{}".format(self.sen_name,port) - for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names): - bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column) - measure_port_name = "{}{}".format(measure_name,port) - self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) - - def write_bitline_delay_measures(self, port): - """ - Write the measure statements to quantify the delay and power results for a read port. - """ - # add measure statements for delays/slews - for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names): - meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port) - self.stim.gen_meas_delay(*meas_values) - - def get_delay_meas_values(self, delay_name, bitline_name, port): - """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - meas_name="{0}{1}".format(delay_name, port) - targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column) - half_vdd = 0.5 * self.vdd_voltage - trig_val = half_vdd - targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"] - trig_name = "clk{0}".format(port) - trig_dir="FALL" - targ_dir="FALL" - #Half period added to delay measurement to negative clock edge - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 - return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) - - def gen_test_cycles_one_port(self, read_port, write_port): - """Sets a list of key time-points [ns] of the waveform (each rising edge) - of the cycles to do a timing evaluation of a single port """ - - # Create the inverse address for a scratch address - inverse_address = self.calculate_inverse_address() - - # For now, ignore data patterns and write ones or zeros - data_ones = "1"*self.word_size - data_zeros = "0"*self.word_size - - if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros) - - self.add_write("W data 1 address {}".format(inverse_address), - inverse_address,data_ones,write_port) - - self.add_write("W data 0 address {} to write value".format(self.probe_address), - self.probe_address,data_zeros,write_port) - self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1 - - # This also ensures we will have a H->L transition on the next read - self.add_read("R data 1 address {} to set DOUT caps".format(inverse_address), - inverse_address,data_zeros,read_port) - self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1 - - self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), - self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 - - 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: - col_address = 0 - bl_column = int(self.sram.words_per_row*probe_data + col_address) - return bl_column - - def run_delay_simulation(self): - """ - This tries to simulate a period and checks if the result works. If - so, it returns True and the delays, slews, and powers. It - 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") - - result = [{} for i in self.all_ports] - # Checking from not data_value to data_value - self.write_delay_stimulus() - - self.stim.run_sim() #running sim prodoces spice output file. - - for port in self.targ_read_ports: - #Parse and check the voltage measurements - bl_volt_meas_dict = {} - for mname in self.bl_volt_meas_names: - mname_port = "{}{}".format(mname,port) - volt_meas_val = parse_spice_list("timing", mname_port) - if type(volt_meas_val)!=float: - debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1) - bl_volt_meas_dict[mname] = volt_meas_val - result[port].update(bl_volt_meas_dict) - - #Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail. - bl_delay_meas_dict = {} - values_added = 0 #For error checking - for mname in self.bl_delay_meas_names: #Parse - mname_port = "{}{}".format(mname,port) - delay_meas_val = parse_spice_list("timing", mname_port) - if type(delay_meas_val)==float: #Only add if value is float, do not error. - bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns - values_added+=1 - debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.") - debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.") - result[port].update(bl_delay_meas_dict) - - # The delay is from the negative edge for our SRAM - return (True,result) - - def check_bitline_all_results(self, results): - """Checks the bitline values measured for each tested port""" - for port in self.targ_read_ports: - self.check_bitline_port_results(results[port]) - - def check_bitline_port_results(self, port_results): - """Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing""" - bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"] - self.check_functionality(bl_volt,br_volt) - self.check_swing_from_vdd(bl_volt,br_volt) - self.check_differential_swing(bl_volt,br_volt) - - def check_functionality(self, bl_volt, br_volt): - """Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0.""" - if bl_volt > br_volt: - debug.error("Read failure. Value 1 was read instead of 0.",1) - - def check_swing_from_vdd(self, bl_volt, br_volt): - """Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter.""" - if bl_volt < br_volt: - discharge_volt = bl_volt - else: - discharge_volt = br_volt - desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage - debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100)) - - def check_differential_swing(self, bl_volt, br_volt): - """This check looks at the difference between the bitline voltages. This needs to be large enough to prevent - sensing errors.""" - bitline_swing = abs(bl_volt-br_volt) - debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing)) - vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. - if bitline_swing < vdd_error_margin*self.vdd_voltage: - debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin)) - - def analyze(self, probe_address, probe_data, slews, loads): - """Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """ - self.set_probe(probe_address, probe_data) - self.load=max(loads) - self.slew=max(slews) - - read_port = self.read_ports[0] #only test the first read port - bitline_swings = {} - self.targ_read_ports = [read_port] - self.targ_write_ports = [self.write_ports[0]] - debug.info(1,"Bitline swing test: corner {}".format(self.corner)) - (success, results)=self.run_delay_simulation() - debug.check(success, "Bitline Failed: period {}".format(self.period)) - debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port])) - self.check_bitline_all_results(results) - - return results - - - diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 315e5268..6cc995e1 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -8,8 +8,18 @@ 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. """ return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance) @@ -32,7 +42,6 @@ def parse_spice_list(filename, key): f.close() # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents) - if val != None: debug.info(4, "Key = " + key + " Val = " + val.group(1)) return convert_to_float(val.group(1)) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 7a579abf..59f5dcf5 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -5,7 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil +import sys,re,shutil,copy import debug import tech import math @@ -17,6 +17,8 @@ from globals import OPTS from .simulation import simulation from .measurements import * import logical_effort +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 @@ -44,6 +46,8 @@ class delay(simulation): self.period = 0 self.set_load_slew(0,0) self.set_corner(corner) + self.create_signal_names() + self.add_graph_exclusions() def create_measurement_names(self): """Create measurement names. The names themselves currently define the type of measurement""" @@ -55,94 +59,258 @@ class delay(simulation): def create_measurement_objects(self): """Create the measurements used for read and write ports""" - self.create_read_port_measurement_objects() - self.create_write_port_measurement_objects() - + 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.""" + 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)) + name_set.add(name) + def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" - self.read_meas_objs = [] - trig_delay_name = "clk{0}" + 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.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. - self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read0" + 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.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 - self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read1" - self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) - self.read_meas_objs[-1].meta_str = "read0" + self.slew_meas = [] + self.slew_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) + self.slew_meas[-1].meta_str = sram_op.READ_ONE + self.slew_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) + self.slew_meas[-1].meta_str = sram_op.READ_ZERO + self.read_lib_meas+=self.slew_meas - self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3)) - self.read_meas_objs[-1].meta_str = "read1" - self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3)) - self.read_meas_objs[-1].meta_str = "read0" + self.read_lib_meas.append(power_measure("read1_power", "RISE", measure_scale=1e3)) + self.read_lib_meas[-1].meta_str = sram_op.READ_ONE + 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. - for obj in self.read_meas_objs: - if obj.meta_str is "read0": + 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 + read_measures.append(self.create_bitline_measurement_objects()) + read_measures.append(self.create_debug_measurement_objects()) + read_measures.append(self.create_read_bit_measures()) - trig_name = "Xsram.s_en{}" #Sense amp enable - if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name - port_format = "" - else: - port_format = "{}" - - bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) - br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) - # self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) - # self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) - - #These are read values but need to be separated for unique error checking. - self.create_bitline_delay_measurement_objects() + return read_measures - def create_bitline_delay_measurement_objects(self): + 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 """ - self.bitline_delay_objs = [] - trig_name = "clk{0}" - if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name - port_format = "" - else: - port_format = "{}" - bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) - br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) - targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd + 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 + self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ZERO", + self.br_name)) + self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO - targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - # self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_objs[-1].meta_str = "read0" - # self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_objs[-1].meta_str = "read1" - #Enforces the time delay on the bitline measurements for read0 or read1 - for obj in self.bitline_delay_objs: - obj.meta_add_delay = True + self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ONE", + 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.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_meas_objs = [] + self.write_lib_meas = [] - self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3)) - self.write_meas_objs[-1].meta_str = "write1" - self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3)) - self.write_meas_objs[-1].meta_str = "write0" + self.write_lib_meas.append(power_measure("write1_power", "RISE", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = sram_op.WRITE_ONE + self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3)) + self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO + + write_measures = [] + write_measures.append(self.write_lib_meas) + return write_measures + + def create_debug_measurement_objects(self): + """Create debug measurement to help identify failures.""" + self.debug_volt_meas = [] + for meas in self.delay_meas: + #Output voltage measures + self.debug_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.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] - def create_signal_names(self): - self.addr_name = "A" - self.din_name = "DIN" - self.dout_name = "DOUT" - - #This is TODO once multiport control has been finalized. - #self.control_name = "CSB" + def create_read_bit_measures(self): + """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: + 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.""" + 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)) + 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 + # 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) + return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} + 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. + self.sram.bank.graph_exclude_precharge() + self.sram.graph_exclude_addr_dff() + self.sram.graph_exclude_data_dff() + self.sram.graph_exclude_ctrl_dffs() + + 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.graph_exclude_bits(self.wordline_row, self.bitline_column) + + #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)) + + 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) + 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.""" + 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 + + def get_bl_name(self, paths): + """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() + + 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_primary_cell_mod(self, cell_mods): + """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) + non_rbc_mods = [] + for bitcell in cell_mods: + has_cell = False + for replica_cell in rbc_mods: + has_cell = has_cell or replica_cell.contains(bitcell, replica_cell.mods) + if not has_cell: + non_rbc_mods.append(bitcell) + if len(non_rbc_mods) != 1: + debug.error('Multiple bitcell mods found. Cannot distinguish for characterization',1) + return non_rbc_mods[0] + + 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 + for mod in mods[1:]: + if pins != mod.pins: + return False + 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""" + 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 + def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" try: @@ -171,12 +339,8 @@ class delay(simulation): # instantiate the sram self.sf.write("\n* Instantiation of the SRAM\n") - self.stim.inst_sram(sram=self.sram, - port_signal_names=(self.addr_name,self.din_name,self.dout_name), - port_info=(len(self.all_ports),self.write_ports,self.read_ports), - abits=self.addr_size, - dbits=self.word_size, - sram_name=self.name) + self.stim.inst_model(pins=self.pins, + model_name=self.sram.name) self.sf.write("\n* SRAM output loads\n") for port in self.read_ports: for i in range(self.word_size): @@ -286,26 +450,35 @@ class delay(simulation): """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: - return self.get_delay_measure_variants(port, measure_obj) + variant_tuple = self.get_delay_measure_variants(port, measure_obj) elif meas_type is power_measure: - return self.get_power_measure_variants(port, measure_obj, "read") + variant_tuple = self.get_power_measure_variants(port, measure_obj, "read") elif meas_type is voltage_when_measure: - return self.get_volt_when_measure_variants(port, measure_obj) + 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. + 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. - if delay_obj.meta_str == "read0": + if delay_obj.meta_str == sram_op.READ_ZERO: #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 == "read1": + 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. if delay_obj.meta_add_delay: meas_cycle_delay += self.period/2 @@ -319,10 +492,24 @@ class delay(simulation): return (t_initial, t_final, port) - def get_volt_when_measure_variants(self, port, power_obj): + 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. - t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + 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 + 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. + t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]] return (t_trig, self.vdd_voltage, port) @@ -331,9 +518,10 @@ 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 measure in self.read_meas_objs+self.bitline_delay_objs: - measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) - measure.write_measure(self.stim, measure_variant_inp_tuple) + 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.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.""" @@ -348,9 +536,10 @@ class delay(simulation): Write the measure statements to quantify the power results for a write port. """ # add measure statements for power - for measure in self.write_meas_objs: - measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) - measure.write_measure(self.stim, measure_variant_inp_tuple) + 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.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): """ @@ -469,6 +658,7 @@ class delay(simulation): #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() @@ -478,26 +668,28 @@ class delay(simulation): #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, "Check delay values for port {}".format(port)) + debug.info(2, "Checking delay values for port {}".format(port)) read_port_dict = {} #Get measurements from output file - for measure in self.read_meas_objs: + 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): + 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. + 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) - bitline_delay_dict = self.evaluate_bitline_delay(port) - result[port].update(bitline_delay_dict) - for port in self.targ_write_ports: write_port_dict = {} - for measure in self.write_meas_objs: + 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): @@ -505,18 +697,94 @@ class delay(simulation): result[port].update(write_port_dict) # The delay is from the negative edge for our SRAM - return (True,result) + return (sim_passed,result) - def evaluate_bitline_delay(self, port): - """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function.""" - bl_delay_meas_dict = {} - values_added = 0 #For error checking - for measure in self.bitline_delay_objs: - bl_delay_val = measure.retrieve_measure(port=port) - if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error. - debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1) - bl_delay_meas_dict[measure.name] = bl_delay_val - return bl_delay_meas_dict + 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)) + 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): + """Debug measures that indicate special conditions.""" + #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 + bl_vals = {} + br_vals = {} + for meas in self.bitline_volt_meas: + val = meas.retrieve_measure(port=port) + if self.bl_name == meas.targ_name_no_port: + bl_vals[meas.meta_str] = val + elif self.br_name == meas.targ_name_no_port: + br_vals[meas.meta_str] = val + + debug.info(2,"{}={}".format(meas.name,val)) + + bl_check = False + for meas in self.debug_volt_meas: + val = meas.retrieve_measure(port=port) + debug.info(2,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + + 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: + debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) + + return success + + + def check_bit_measures(self): + """Checks the measurements which represent the internal storage voltages + at the end of the read cycle.""" + success = True + for polarity, meas_list in self.bit_meas.items(): + for meas in meas_list: + val = meas.retrieve_measure() + debug.info(2,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + meas_cycle = meas.meta_str + #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 + 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)) + 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. + min_dicharge = v_discharged_bl < self.vdd_voltage*0.9 + min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage*0.1 + + debug.info(1,"min_dicharge={}, min_diff={}".format(min_dicharge,min_diff)) + return (min_dicharge and min_diff) def run_power_simulation(self): """ @@ -563,7 +831,8 @@ 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 - if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.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, delays_str, slews_str)) @@ -712,31 +981,24 @@ class delay(simulation): # Make a copy in temp for debugging shutil.copy(self.sp_file, self.sim_sp_file) - - - def analyze(self,probe_address, probe_data, slews, loads): + 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() + self.create_measurement_names() + self.create_measurement_objects() + + def analyze(self, probe_address, probe_data, slews, loads): """ Main function to characterize an SRAM for a table. Computes both delay and power characterization. """ #Dict to hold all characterization values char_sram_data = {} - - self.set_probe(probe_address, probe_data) - self.create_signal_names() - self.create_measurement_names() - self.create_measurement_objects() + self.analysis_init(probe_address, probe_data) self.load=max(loads) self.slew=max(slews) - # This is for debugging a full simulation - # debug.info(0,"Debug simulation running...") - # target_period=50.0 - # feasible_delay_lh=0.059083183 - # feasible_delay_hl=0.17953789 - # load=1.6728 - # slew=0.04 - # self.try_period(target_period, feasible_delay_lh, feasible_delay_hl) - # sys.exit(1) # 1) Find a feasible period and it's corresponding delays using the trimmed array. feasible_delays = self.find_feasible_period() @@ -826,7 +1088,7 @@ class delay(simulation): self.add_write("W data 0 address {} to write value".format(self.probe_address), self.probe_address,data_zeros,write_port) - self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1 # This also ensures we will have a H->L transition on the next read self.add_read("R data 1 address {} to set DOUT caps".format(inverse_address), @@ -834,14 +1096,14 @@ class delay(simulation): self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 + self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)", inverse_address,data_zeros) self.add_write("W data 1 address {} to write value".format(self.probe_address), self.probe_address,data_ones,write_port) - self.measure_cycles[write_port]["write1"] = len(self.cycle_times)-1 + self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 self.add_write("W data 0 address {} to clear DIN caps".format(inverse_address), inverse_address,data_zeros,write_port) @@ -852,7 +1114,7 @@ class delay(simulation): self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), self.probe_address,data_zeros,read_port) - self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1 + self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times)-1 self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))", self.probe_address,data_zeros) @@ -908,7 +1170,6 @@ class delay(simulation): """ 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.") - self.create_signal_names() self.create_measurement_names() power = self.analytical_power(slews, loads) port_data = self.get_empty_measure_data_dict() diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 7c9517fc..939ecf96 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -201,12 +201,7 @@ class functional(simulation): new_value = "0" + new_value #print("Binary Conversion: {} to {}".format(value, new_value)) - return new_value - - def create_signal_names(self): - self.addr_name = "A" - self.din_name = "DIN" - self.dout_name = "DOUT" + return new_value def write_functional_stimulus(self): """ Writes SPICE stimulus. """ @@ -226,12 +221,8 @@ class functional(simulation): #Instantiate the SRAM self.sf.write("\n* Instantiation of the SRAM\n") - self.stim.inst_sram(sram=self.sram, - port_signal_names=(self.addr_name,self.din_name,self.dout_name), - port_info=(len(self.all_ports), self.write_ports, self.read_ports), - abits=self.addr_size, - dbits=self.word_size, - sram_name=self.name) + self.stim.inst_model(pins=self.pins, + model_name=self.sram.name) # Add load capacitance to each of the read ports self.sf.write("\n* SRAM output loads\n") diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index 8fec4601..54f9973f 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -13,10 +13,11 @@ from .charutils import * class spice_measurement(ABC): """Base class for spice stimulus measurements.""" - def __init__(self, measure_name, measure_scale=None): + def __init__(self, measure_name, measure_scale=None, has_port=True): #Names must be unique for correct spice simulation, but not enforced here. self.name = measure_name self.measure_scale = measure_scale + self.has_port = has_port #Needed for error checking #Some meta values used externally. variables are added here for consistency accross the objects self.meta_str = None self.meta_add_delay = False @@ -35,18 +36,29 @@ class spice_measurement(ABC): measure_vals = self.get_measure_values(*input_tuple) measure_func(stim_obj, *measure_vals) - def retrieve_measure(self, port=""): - value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + def retrieve_measure(self, port=None): + self.port_error_check(port) + if port != None: + value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + else: + value = parse_spice_list("timing", "{0}".format(self.name.lower())) if type(value)!=float or self.measure_scale == None: return value else: return value*self.measure_scale - + + def port_error_check(self, port): + if self.has_port and port == None: + debug.error("Cannot retrieve measurement, port input was expected.",1) + elif not self.has_port and port != None: + debug.error("Unexpected port input received during measure retrieval.",1) + class delay_measure(spice_measurement): """Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\ + trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd) def get_measure_function(self): @@ -56,10 +68,8 @@ class delay_measure(spice_measurement): """Set the constants for this measurement: signal names, directions, and trigger scales""" self.trig_dir_str = trig_dir_str self.targ_dir_str = targ_dir_str - self.trig_val_of_vdd = trig_vdd self.targ_val_of_vdd = targ_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name @@ -67,9 +77,10 @@ class delay_measure(spice_measurement): def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) trig_val = self.trig_val_of_vdd * vdd_voltage targ_val = self.targ_val_of_vdd * vdd_voltage - + if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) @@ -79,13 +90,12 @@ class delay_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td) class slew_measure(delay_measure): - def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(signal_name, slew_dir_str) def set_meas_constants(self, signal_name, slew_dir_str): @@ -101,7 +111,6 @@ class slew_measure(delay_measure): self.targ_val_of_vdd = 0.1 else: debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1) - self.trig_name_no_port = signal_name self.targ_name_no_port = signal_name @@ -110,8 +119,8 @@ class slew_measure(delay_measure): class power_measure(spice_measurement): """Generates a spice measurement for the average power between two time points.""" - def __init__(self, measure_name, power_type="", measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(power_type) def get_measure_function(self): @@ -124,6 +133,7 @@ class power_measure(spice_measurement): def get_measure_values(self, t_initial, t_final, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) if port != None: meas_name = "{}{}".format(self.name, port) else: @@ -133,8 +143,8 @@ class power_measure(spice_measurement): class voltage_when_measure(spice_measurement): """Generates a spice measurement to measure the voltage of a signal based on the voltage of another.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd) def get_measure_function(self): @@ -144,13 +154,12 @@ class voltage_when_measure(spice_measurement): """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" self.trig_dir_str = trig_dir_str self.trig_val_of_vdd = trig_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name def get_measure_values(self, trig_td, vdd_voltage, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" - + self.port_error_check(port) if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) @@ -160,7 +169,33 @@ class voltage_when_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - - trig_voltage = self.trig_val_of_vdd*vdd_voltage - - return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) \ No newline at end of file + trig_voltage = self.trig_val_of_vdd*vdd_voltage + return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) + +class voltage_at_measure(spice_measurement): + """Generates a spice measurement to measure the voltage at a specific time. + The time is considered variant with different periods.""" + + def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) + self.set_meas_constants(targ_name) + + def get_measure_function(self): + return stimuli.gen_meas_find_voltage_at_time + + def set_meas_constants(self, targ_name): + """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" + self.targ_name_no_port = targ_name + + def get_measure_values(self, time_at, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) + if port != None: + #For dictionary indexing reasons, the name is formatted differently than the signals + meas_name = "{}{}".format(self.name, port) + targ_name = self.targ_name_no_port.format(port) + else: + meas_name = self.name + targ_name = self.targ_name_no_port + return (meas_name,targ_name,time_at) + \ No newline at end of file diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py index e06c1dd2..b35c3c47 100644 --- a/compiler/characterizer/model_check.py +++ b/compiler/characterizer/model_check.py @@ -38,7 +38,7 @@ class model_check(delay): self.bl_meas_name, self.bl_slew_name = "bl_measures", "bl_slews" self.power_name = "total_power" - def create_measurement_names(self): + def create_measurement_names(self, port): """Create measurement names. The names themselves currently define the type of measurement""" #Create delay measurement names wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] @@ -49,7 +49,10 @@ class model_check(delay): else: dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"] - self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + if port not in self.sram.readonly_ports: + self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + else: + self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"] # if self.custom_delaychain: @@ -65,45 +68,54 @@ class model_check(delay): else: dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"] - self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + if port not in self.sram.readonly_ports: + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + else: + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"] self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"] self.power_meas_names = ['read0_power'] - def create_signal_names(self): + def create_signal_names(self, port): """Creates list of the signal names used in the spice file along the wl and sen paths. Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are replicated here. """ delay.create_signal_names(self) #Signal names are all hardcoded, need to update to make it work for probe address and different configurations. - wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] - wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] - sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + wl_en_driver_signals = ["Xsram.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())] + wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] + sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())] if self.custom_delaychain: delay_chain_signal_names = [] else: - delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] - - self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\ + delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())] + if len(self.sram.all_ports) > 1: + port_format = '{}' + else: + port_format = '' + self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\ wl_en_driver_signals+\ - ["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\ + ["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\ wl_driver_signals+\ - ["Xsram.Xbank0.wl_{}".format(self.wordline_row)] - pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)] + pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')] + if port not in self.sram.readonly_ports: + pre_delay_chain_names+= ["Xsram.Xcontrol{}.Xand2_rbl_in.zb_int".format('{}'), "Xsram.Xcontrol{}.rbl_in".format('{}')] + self.rbl_en_signal_names = pre_delay_chain_names+\ delay_chain_signal_names+\ - ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"] + ["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')] - self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\ + self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\ sen_driver_signals+\ - ["Xsram.s_en0"] + ["Xsram.s_en{}".format('{}')] dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - self.bl_signal_names = ["Xsram.Xbank0.wl_{}".format(self.wordline_row),\ - "Xsram.Xbank0.bl_{}".format(self.bitline_column),\ + self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\ + "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\ dout_name] def create_measurement_objects(self): @@ -369,17 +381,18 @@ class model_check(delay): errors = self.calculate_error_l2_norm(scaled_meas, scaled_model) debug.info(1, "Errors:\n{}\n".format(errors)) - def analyze(self, probe_address, probe_data, slews, loads): + def analyze(self, probe_address, probe_data, slews, loads, port): """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays.""" self.load=max(loads) self.slew=max(slews) self.set_probe(probe_address, probe_data) - self.create_signal_names() - self.create_measurement_names() + self.create_signal_names(port) + self.create_measurement_names(port) self.create_measurement_objects() data_dict = {} read_port = self.read_ports[0] #only test the first read port + read_port = port self.targ_read_ports = [read_port] self.targ_write_ports = [self.write_ports[0]] debug.info(1,"Model test: corner {}".format(self.corner)) diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index afbef154..83d367b0 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -49,6 +49,19 @@ class simulation(): self.v_low = tech.spice["v_threshold_typical"] self.gnd_voltage = 0 + def create_signal_names(self): + self.addr_name = "A" + self.din_name = "DIN" + self.dout_name = "DOUT" + self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name), + port_info=(len(self.all_ports),self.write_ports,self.read_ports), + abits=self.addr_size, + dbits=self.word_size) + debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ + do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,self.pins)) + #This is TODO once multiport control has been finalized. + #self.control_name = "CSB" + def set_stimulus_variables(self): # Clock signals self.cycle_times = [] @@ -230,3 +243,38 @@ class simulation(): t_current+self.period) return comment + def gen_pin_names(self, port_signal_names, port_info, abits, dbits): + """Creates the pins names of the SRAM based on the no. of ports.""" + #This may seem redundant as the pin names are already defined in the sram. However, it is difficult + #to extract the functionality from the names, so they are recreated. As the order is static, changing + #the order of the pin names will cause issues here. + pin_names = [] + (addr_name, din_name, dout_name) = port_signal_names + (total_ports, write_index, read_index) = port_info + + for write_input in write_index: + for i in range(dbits): + pin_names.append("{0}{1}_{2}".format(din_name,write_input, i)) + + for port in range(total_ports): + for i in range(abits): + pin_names.append("{0}{1}_{2}".format(addr_name,port,i)) + + #Control signals not finalized. + for port in range(total_ports): + pin_names.append("CSB{0}".format(port)) + for port in range(total_ports): + if (port in read_index) and (port in write_index): + pin_names.append("WEB{0}".format(port)) + + for port in range(total_ports): + pin_names.append("{0}{1}".format(tech.spice["clk"], port)) + + for read_output in read_index: + for i in range(dbits): + pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i)) + + pin_names.append("{0}".format(tech.spice["vdd_name"])) + pin_names.append("{0}".format(tech.spice["gnd_name"])) + return pin_names + diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index aef09ae1..31ee24d1 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -36,51 +36,15 @@ class stimuli(): (self.process, self.voltage, self.temperature) = corner self.device_models = tech.spice["fet_models"][self.process] - def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name): + self.sram_name = "Xsram" + + def inst_sram(self, pins, inst_name): """ Function to instatiate an SRAM subckt. """ - pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits) - #Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM - debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names)) - - self.sf.write("Xsram ") - for pin in pin_names: + + self.sf.write("{} ".format(self.sram_name)) + for pin in self.sram_pins: self.sf.write("{0} ".format(pin)) - self.sf.write("{0}\n".format(sram_name)) - - def gen_pin_names(self, port_signal_names, port_info, abits, dbits): - """Creates the pins names of the SRAM based on the no. of ports.""" - #This may seem redundant as the pin names are already defined in the sram. However, it is difficult to extract the - #functionality from the names, so they are recreated. As the order is static, changing the order of the pin names - #will cause issues here. - pin_names = [] - (addr_name, din_name, dout_name) = port_signal_names - (total_ports, write_index, read_index) = port_info - - for write_input in write_index: - for i in range(dbits): - pin_names.append("{0}{1}_{2}".format(din_name,write_input, i)) - - for port in range(total_ports): - for i in range(abits): - pin_names.append("{0}{1}_{2}".format(addr_name,port,i)) - - #Control signals not finalized. - for port in range(total_ports): - pin_names.append("CSB{0}".format(port)) - for port in range(total_ports): - if (port in read_index) and (port in write_index): - pin_names.append("WEB{0}".format(port)) - - for port in range(total_ports): - pin_names.append("{0}{1}".format(tech.spice["clk"], port)) - - for read_output in read_index: - for i in range(dbits): - pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i)) - - pin_names.append("{0}".format(self.vdd_name)) - pin_names.append("{0}".format(self.gnd_name)) - return pin_names + self.sf.write("{0}\n".format(inst_name)) def inst_model(self, pins, model_name): """ Function to instantiate a generic model with a set of pins """ @@ -233,6 +197,13 @@ class stimuli(): trig_dir, trig_td)) + def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): + """ Creates the .meas statement for voltage at time""" + measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" + self.sf.write(measure_string.format(meas_name, + targ_name, + time_at)) + def gen_meas_power(self, meas_name, t_initial, t_final): """ Creates the .meas statement for the measurement of avg power """ # power mea cmd is different in different spice: diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index b84ad678..78934887 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -405,18 +405,17 @@ class bank(design.design): def add_modules(self): """ Add all the modules using the class loader """ - - self.bitcell_array = factory.create(module_type="bitcell_array", - cols=self.num_cols, - rows=self.num_rows) - self.add_mod(self.bitcell_array) - # 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.precharge_array = [] for port in self.all_ports: @@ -1285,3 +1284,13 @@ class bank(design.design): """Get the relative capacitance of all the sense amp enable connections in the bank""" #Current bank only uses sen as an enable for the sense amps. return self.sense_amp_array.get_en_cin() + + def graph_exclude_precharge(self): + """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" + for inst in self.precharge_array_inst: + if inst != None: + self.graph_inst_exclude.add(inst) + + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + return self.bitcell_array_inst.mod.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col) \ No newline at end of file diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 01b64c11..15141bb3 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -156,7 +156,7 @@ class 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) @@ -202,3 +202,16 @@ class 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""" + #Function is not robust with column mux configurations + for row in range(self.row_size): + for col in range(self.column_size): + if row == targ_row and col == targ_col: + continue + self.graph_inst_exclude.add(self.cell_inst[row,col]) + + 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 diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 643a62c2..74fb86c5 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -144,10 +144,10 @@ class control_logic(design.design): 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 * parameter["rbl_height_percentage"])) + 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 = parameter["static_fanout_list"] + 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, @@ -971,3 +971,7 @@ class control_logic(design.design): if self.port_type == 'rw': total_cin +=self.and2.get_cin() return total_cin + + 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) diff --git a/compiler/modules/dff.py b/compiler/modules/dff.py index f67d1e00..c8d7414a 100644 --- a/compiler/modules/dff.py +++ b/compiler/modules/dff.py @@ -18,6 +18,7 @@ class dff(design.design): """ pin_names = ["D", "Q", "clk", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"]) @@ -27,6 +28,7 @@ class dff(design.design): self.width = dff.width self.height = dff.height self.pin_map = dff.pin_map + self.add_pin_types(self.type_list) def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" @@ -57,3 +59,7 @@ class dff(design.design): #Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width. return parameter["dff_clk_cin"] + 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 diff --git a/compiler/modules/hierarchical_decoder.py b/compiler/modules/hierarchical_decoder.py index 44a6bfb2..4f359c95 100644 --- a/compiler/modules/hierarchical_decoder.py +++ b/compiler/modules/hierarchical_decoder.py @@ -624,3 +624,4 @@ class hierarchical_decoder(design.design): else: pre = self.pre3_8 return pre.input_load() + diff --git a/compiler/modules/precharge_array.py b/compiler/modules/precharge_array.py index 8168b7af..bf45afd5 100644 --- a/compiler/modules/precharge_array.py +++ b/compiler/modules/precharge_array.py @@ -115,3 +115,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_bitline.py b/compiler/modules/replica_bitline.py index 6abf93e3..2a441f02 100644 --- a/compiler/modules/replica_bitline.py +++ b/compiler/modules/replica_bitline.py @@ -25,6 +25,8 @@ class replica_bitline(design.design): 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: @@ -86,7 +88,7 @@ class replica_bitline(design.design): 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, diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 0afdcd99..e104356f 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -20,6 +20,7 @@ class sense_amp(design.design): """ pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"] + type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"]) @@ -30,7 +31,8 @@ class sense_amp(design.design): self.width = sense_amp.width self.height = sense_amp.height self.pin_map = sense_amp.pin_map - + self.add_pin_types(self.type_list) + def input_load(self): #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. from tech import spice, parameter @@ -58,4 +60,16 @@ class sense_amp(design.design): pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. - return 2*pmos_cin + nmos_cin \ No newline at end of file + return 2*pmos_cin + nmos_cin + + def get_enable_name(self): + """Returns name used for enable net""" + #FIXME: A better programmatic solution to designate pins + enable_name = "en" + debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name)) + return enable_name + + 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 diff --git a/compiler/modules/tri_gate.py b/compiler/modules/tri_gate.py index 6721c5ba..3907a03b 100644 --- a/compiler/modules/tri_gate.py +++ b/compiler/modules/tri_gate.py @@ -17,7 +17,8 @@ class tri_gate(design.design): netlist should be available in the technology library. """ - pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"] + pin_names = ["in", "out", "en", "en_bar", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"]) @@ -33,6 +34,7 @@ class tri_gate(design.design): self.width = tri_gate.width self.height = tri_gate.height self.pin_map = tri_gate.pin_map + self.add_pin_types(self.type_list) def analytical_delay(self, corner, slew, load=0.0): from tech import spice @@ -49,3 +51,6 @@ class tri_gate(design.design): def input_load(self): return 9*spice["min_tx_gate_c"] + 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 diff --git a/compiler/modules/write_driver.py b/compiler/modules/write_driver.py index 801ae913..85a58fd5 100644 --- a/compiler/modules/write_driver.py +++ b/compiler/modules/write_driver.py @@ -18,7 +18,8 @@ class write_driver(design.design): the technology library. """ - pin_names = ["din", "bl", "br", "en", "gnd", "vdd"] + pin_names = ["din", "bl", "br", "en", "vdd", "gnd"] + type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"]) @@ -29,9 +30,13 @@ class write_driver(design.design): self.width = write_driver.width self.height = write_driver.height self.pin_map = write_driver.pin_map - + self.add_pin_types(self.type_list) def get_w_en_cin(self): """Get the relative capacitance of a single input""" # This is approximated from SCMOS. It has roughly 5 3x transistor gates. return 5*3 + + 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 diff --git a/compiler/options.py b/compiler/options.py index 354c2470..833d5289 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -46,10 +46,15 @@ class options(optparse.Values): ################### # Optimization options - ################### - # Uses the delay chain size in the tech.py file rather automatic sizing. + ################### + rbl_delay_percentage = .5 #Approximate percentage of delay compared to bitlines + + # Allow manual adjustment of the delay chain over automatic use_tech_delay_chain_size = False - + delay_chain_stages = 4 + delay_chain_fanout_per_stage = 3 + + ################### # Debug options. diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index 25f57cdd..546eb830 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -18,7 +18,7 @@ class pand2(pgate.pgate): This is a simple buffer used for driving loads. """ def __init__(self, name, size=1, height=None): - debug.info(1, "reating pnand2 {}".format(name)) + debug.info(1, "Creating pnand2 {}".format(name)) self.add_comment("size: {}".format(size)) self.size = size diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index c7cc47e8..f974f0c4 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -246,4 +246,3 @@ class pgate(design.design): # offset=implant_offset, # width=implant_width, # height=implant_height) - diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 9a41321f..906a009d 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -37,7 +37,6 @@ class pinv(pgate.pgate): self.beta = beta self.route_output = False - # Creates the netlist and layout pgate.pgate.__init__(self, name, height) def create_netlist(self): @@ -60,7 +59,9 @@ class pinv(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "Z", "vdd", "gnd"]) + pin_list = ["A", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def determine_tx_mults(self): @@ -281,7 +282,8 @@ class pinv(pgate.pgate): return transition_prob*(c_load + c_para) def get_cin(self): - """Return the capacitance of the gate connection in generic capacitive units relative to the minimum width of a transistor""" + """Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor""" return self.nmos_size + self.pmos_size def get_stage_effort(self, cout, inp_is_rise=True): @@ -289,4 +291,13 @@ class pinv(pgate.pgate): Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ parasitic_delay = 1 - return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + return logical_effort.logical_effort(self.name, + self.size, + self.get_cin(), + cout, + parasitic_delay, + not inp_is_rise) + + 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 diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index c20bfdaa..6e3fb7ae 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -38,6 +38,8 @@ class pnand2(pgate.pgate): # Creates the netlist and layout pgate.pgate.__init__(self, name, height) + #For characterization purposes only + #self.exclude_nmos_from_graph() def create_netlist(self): self.add_pins() @@ -58,7 +60,9 @@ class pnand2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def add_ptx(self): @@ -267,3 +271,14 @@ class pnand2(pgate.pgate): """ parasitic_delay = 2 return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + + def exclude_nmos_from_graph(self): + """Exclude the nmos TXs from the graph to reduce complexity""" + #The pull-down network has an internal net which causes 2 different in->out paths + #Removing them simplifies generic path searching. + self.graph_inst_exclude.add(self.nmos1_inst) + self.graph_inst_exclude.add(self.nmos2_inst) + + 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 diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 0f2d0c56..a8cce176 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -43,7 +43,9 @@ class pnand3(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "C", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND'] + self.add_pin_list(pin_list, dir_list) def create_netlist(self): self.add_pins() @@ -278,3 +280,7 @@ class pnand3(pgate.pgate): """ parasitic_delay = 3 return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + + 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 diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index 5f0c4de5..7f026da3 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -40,7 +40,9 @@ class pnor2(pgate.pgate): def add_pins(self): """ Adds pins for spice netlist """ - self.add_pin_list(["A", "B", "Z", "vdd", "gnd"]) + pin_list = ["A", "B", "Z", "vdd", "gnd"] + dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'INOUT', 'INOUT'] + self.add_pin_list(pin_list, dir_list) def create_netlist(self): self.add_pins() @@ -238,3 +240,6 @@ class pnor2(pgate.pgate): transition_prob = spice["nor2_transition_prob"] return transition_prob*(c_load + c_para) + 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 diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index a0d9b484..c080a4ba 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -68,7 +68,13 @@ class ptx(design.design): #self.DRC() def create_netlist(self): - self.add_pin_list(["D", "G", "S", "B"]) + pin_list = ["D", "G", "S", "B"] + if self.tx_type=="nmos": + body_dir = 'GROUND' + else: #Assumed that the check for either pmos or nmos is done elsewhere. + body_dir = 'POWER' + dir_list = ['INOUT', 'INPUT', 'INOUT', body_dir] + self.add_pin_list(pin_list, dir_list) # self.spice.append("\n.SUBCKT {0} {1}".format(self.name, # " ".join(self.pins))) @@ -366,3 +372,8 @@ class ptx(design.design): def get_cin(self): """Returns the relative gate cin of the tx""" return self.tx_width/drc("minwidth_tx") + + 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 diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index c5ad07e9..7025d228 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -309,3 +309,46 @@ class sram_1bank(sram_base): self.add_label(text=n, layer=pin.layer, offset=pin.center()) + + def graph_exclude_data_dff(self): + """Removes data dff from search graph. """ + #Data dffs are only for writing so are not useful for evaluating read delay. + for inst in self.data_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_addr_dff(self): + """Removes data dff from search graph. """ + #Address is considered not part of the critical path, subjectively removed + for inst in self.row_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + if self.col_addr_dff: + for inst in self.col_addr_dff_insts: + self.graph_inst_exclude.add(inst) + + def graph_exclude_ctrl_dffs(self): + """Exclude dffs for CSB, WEB, etc from graph""" + #Insts located in control logic, exclusion function called here + for inst in self.control_logic_insts: + inst.mod.graph_exclude_dffs() + + def get_sen_name(self, sram_name, port=0): + """Returns the s_en spice name.""" + #Naming scheme is hardcoded using this function, should be built into the + #graph in someway. + sen_name = "s_en{}".format(port) + control_conns = self.get_conns(self.control_logic_insts[port]) + #Sanity checks + if sen_name not in control_conns: + debug.error("Signal={} not contained in control logic connections={}"\ + .format(sen_name, control_conns)) + if sen_name in self.pins: + debug.error("Internal signal={} contained in port list. Name defined by the parent.") + return "X{}.{}".format(sram_name, sen_name) + + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + #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 diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 931b11c4..d5b56242 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -543,6 +543,7 @@ class sram_base(design, verilog, lef): elif port in self.readwrite_ports: control_logic = self.control_logic_rw else: + delays[port] = self.return_delay(0,0) #Write ports do not have a lib defined delay, marked as 0 continue clk_to_wlen_delays = control_logic.analytical_delay(corner, slew, load) wlen_to_dout_delays = self.bank.analytical_delay(corner,slew,load,port) #port should probably be specified... diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 55cfc895..0083841d 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -44,17 +44,15 @@ class sram_factory: if hasattr(OPTS, module_type): # Retrieve the name from OPTS if it exists, # otherwise just use the name - module_name = getattr(OPTS, module_type) - else: - module_name = module_type - + module_type = getattr(OPTS, module_type) + # Either retrieve the already loaded module or load it try: mod = self.modules[module_type] except KeyError: import importlib - c = importlib.reload(__import__(module_name)) - mod = getattr(c, module_name) + c = importlib.reload(__import__(module_type)) + mod = getattr(c, module_type) self.modules[module_type] = mod self.module_indices[module_type] = 0 self.objects[module_type] = [] @@ -74,15 +72,29 @@ class sram_factory: # This is especially for library cells so that the spice and gds files can be found. if len(kwargs)>0: # Create a unique name and increment the index - module_name = "{0}_{1}".format(module_name, self.module_indices[module_type]) + module_name = "{0}_{1}".format(module_type, self.module_indices[module_type]) self.module_indices[module_type] += 1 + else: + module_name = module_type #debug.info(0, "New module: type={0} name={1} kwargs={2}".format(module_type,module_name,str(kwargs))) obj = mod(name=module_name,**kwargs) self.objects[module_type].append((kwargs,obj)) return obj - + def get_mods(self, module_type): + """Returns list of all objects of module name's type.""" + if hasattr(OPTS, module_type): + # Retrieve the name from OPTS if it exists, + # otherwise just use the input + module_type = getattr(OPTS, module_type) + try: + mod_tuples = self.objects[module_type] + mods = [mod for kwargs,mod in mod_tuples] + except KeyError: + mods = [] + return mods + # Make a factory factory = sram_factory() diff --git a/compiler/tests/05_bitcell_1rw_1r_array_test.py b/compiler/tests/05_bitcell_1rw_1r_array_test.py index 681dd33e..275e19c1 100755 --- a/compiler/tests/05_bitcell_1rw_1r_array_test.py +++ b/compiler/tests/05_bitcell_1rw_1r_array_test.py @@ -33,7 +33,7 @@ class bitcell_1rw_1r_array_test(openram_test): OPTS.num_w_ports = 0 a = factory.create(module_type="bitcell_array", cols=4, rows=4) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py index 726fee50..9d522391 100755 --- a/compiler/tests/05_bitcell_array_test.py +++ b/compiler/tests/05_bitcell_array_test.py @@ -29,7 +29,7 @@ class array_test(openram_test): debug.info(2, "Testing 4x4 array for 6t_cell") a = factory.create(module_type="bitcell_array", cols=4, rows=4) self.local_check(a) - + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/12_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py index ba08711b..1dd347a1 100755 --- a/compiler/tests/12_tri_gate_array_test.py +++ b/compiler/tests/12_tri_gate_array_test.py @@ -27,7 +27,7 @@ class tri_gate_array_test(openram_test): debug.info(1, "Testing tri_gate_array for columns=8, word_size=8") a = factory.create(module_type="tri_gate_array", columns=8, word_size=8) self.local_check(a) - + debug.info(1, "Testing tri_gate_array for columns=16, word_size=8") a = factory.create(module_type="tri_gate_array", columns=16, word_size=8) self.local_check(a) diff --git a/compiler/tests/16_control_logic_multiport_test.py b/compiler/tests/16_control_logic_multiport_test.py new file mode 100755 index 00000000..66c34d24 --- /dev/null +++ b/compiler/tests/16_control_logic_multiport_test.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +#Copyright (c) 2016-2019 Regents of the University of California and The Board +#of Regents for the Oklahoma Agricultural and Mechanical College +#(acting for and on behalf of Oklahoma State University) +#All rights reserved. +# +""" +Run a regression test on a control_logic +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + +class control_logic_test(openram_test): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import control_logic + import tech + + # check control logic for multi-port + OPTS.bitcell = "pbitcell" + OPTS.replica_bitcell = "replica_pbitcell" + OPTS.num_rw_ports = 1 + OPTS.num_w_ports = 1 + OPTS.num_r_ports = 1 + + debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") + self.local_check(a) + + # OPTS.num_rw_ports = 0 + # OPTS.num_w_ports = 1 + debug.info(1, "Testing sample for control_logic for multiport, only write control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") + self.local_check(a) + + # OPTS.num_w_ports = 0 + # OPTS.num_r_ports = 1 + debug.info(1, "Testing sample for control_logic for multiport, only read control logic") + a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r") + self.local_check(a) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index 6571e3aa..804d9c65 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -31,40 +31,6 @@ class control_logic_test(openram_test): a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32) self.local_check(a) - # check control logic for multi-port - OPTS.bitcell = "pbitcell" - OPTS.replica_bitcell = "replica_pbitcell" - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for control_logic for multiport") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8) - self.local_check(a) - - # Check port specific control logic - OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 0 - - debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw") - self.local_check(a) - - OPTS.num_rw_ports = 0 - OPTS.num_w_ports = 1 - debug.info(1, "Testing sample for control_logic for multiport, only write control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w") - self.local_check(a) - - OPTS.num_w_ports = 0 - OPTS.num_r_ports = 1 - debug.info(1, "Testing sample for control_logic for multiport, only read control logic") - a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r") - self.local_check(a) - - globals.end_openram() - # run the test from the command line if __name__ == "__main__": (OPTS, args) = globals.parse_args() diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 06bfe44c..f4d3244b 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -37,10 +37,17 @@ class timing_sram_test(openram_test): num_words=16, num_banks=1) c.words_per_row=1 + # c = sram_config(word_size=32, + # num_words=256, + # num_banks=1) + # c.words_per_row=2 + # OPTS.use_tech_delay_chain_size = True c.recompute_sizes() debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = factory.create(module_type="sram", sram_config=c) - + #import sys + #sys.exit(1) + tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 28313115..d4904e09 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -70,11 +70,8 @@ class openram_test(unittest.TestCase): """ debug.info(1, "Finding feasible period for current test.") delay_obj.set_load_slew(load, slew) - delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. - delay_obj.create_signal_names() - delay_obj.create_measurement_names() - delay_obj.create_measurement_objects() + delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) delay_obj.find_feasible_period_one_port(test_port) return delay_obj.period diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 1f1f84a8..84d1f2db 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -354,9 +354,6 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no #Parameters related to sense amp enable timing and delay chain/RBL sizing parameter['le_tau'] = 2.25 #In pico-seconds. parameter['cap_relative_per_ff'] = 7.5 #Units of Relative Capacitance/ Femto-Farad -parameter["static_delay_stages"] = 4 -parameter["static_fanout_per_stage"] = 3 -parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["dff_clk_cin"] = 30.6 #relative capacitance parameter["6tcell_wl_cin"] = 3 #relative capacitance parameter["min_inv_para_delay"] = 2.4 #Tau delay units @@ -364,7 +361,6 @@ parameter["sa_en_pmos_size"] = .72 #micro-meters parameter["sa_en_nmos_size"] = .27 #micro-meters parameter["sa_inv_pmos_size"] = .54 #micro-meters parameter["sa_inv_nmos_size"] = .27 #micro-meters -parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter['bitcell_drain_cap'] = 0.1 #In Femto-Farad, approximation of drain capacitance ################################################### diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 616c38fa..9d0e8aad 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -321,16 +321,12 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no parameter['le_tau'] = 23 #In pico-seconds. parameter["min_inv_para_delay"] = .73 #In relative delay units parameter['cap_relative_per_ff'] = .91 #Units of Relative Capacitance/ Femto-Farad -parameter["static_delay_stages"] = 4 -parameter["static_fanout_per_stage"] = 3 -parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]] parameter["dff_clk_cin"] = 27.5 #In relative capacitance units parameter["6tcell_wl_cin"] = 2 #In relative capacitance units parameter["sa_en_pmos_size"] = 24*_lambda_ parameter["sa_en_nmos_size"] = 9*_lambda_ parameter["sa_inv_pmos_size"] = 18*_lambda_ parameter["sa_inv_nmos_size"] = 9*_lambda_ -parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array parameter['bitcell_drain_cap'] = 0.2 #In Femto-Farad, approximation of drain capacitance ###################################################