diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index 5d1ee692..6f8af016 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -40,9 +40,9 @@ class timing_graph(): """Helper function to remove edges, useful for removing vdd/gnd""" node = node.lower() - self.graph[node] = set() + self.graph[node] = set() - def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True): + def get_all_paths(self, src_node, dest_node, remove_rail_nodes=True, reduce_paths=True): """Traverse all paths from source to destination""" src_node = src_node.lower() @@ -59,44 +59,44 @@ class timing_graph(): visited = set() # Create an array to store paths - path = [] + path = [] self.all_paths = [] # Call the recursive helper function to print all paths - self.get_all_paths_util(src_node, dest_node, visited, path) + self.get_all_paths_util(src_node, dest_node, visited, path) debug.info(2, "Paths found={}".format(len(self.all_paths))) if reduce_paths: self.reduce_paths() - return self.all_paths + return self.all_paths def reduce_paths(self): """ Remove any path that is a subset of another path """ self.all_paths = [p1 for p1 in self.all_paths if not any(set(p1)<=set(p2) for p2 in self.all_paths if p1 is not p2)] - def get_all_paths_util(self, cur_node, dest_node, visited, path): + 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) + path.append(cur_node) # If current vertex is same as destination, then print # current path[] - if cur_node == dest_node: + if cur_node == dest_node: self.all_paths.append(copy.deepcopy(path)) - else: + 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) + 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) + path.pop() + visited.remove(cur_node) def get_timing(self, path, corner, slew, load): """Returns the analytical delays in the input path""" @@ -106,20 +106,20 @@ class timing_graph(): delays = [] cur_slew = slew - for i in range(len(path)-1): + for i in range(len(path) - 1): - path_edge_mod = self.edge_mods[(path[i], path[i+1])] + path_edge_mod = self.edge_mods[(path[i], path[i + 1])] # On the output of the current stage, get COUT from all other mods connected cout = 0 - for node in self.graph[path[i+1]]: - output_edge_mod = self.edge_mods[(path[i+1], node)] + for node in self.graph[path[i + 1]]: + output_edge_mod = self.edge_mods[(path[i + 1], node)] cout+=output_edge_mod.get_cin() # If at the last output, include the final output load - if i == len(path)-2: - cout+=load + if i == len(path) - 2: + cout += load - delays.append(path_edge_mod.analytical_delay(corner, slew, cout)) + delays.append(path_edge_mod.analytical_delay(corner, slew, cout)) cur_slew = delays[-1].slew return delays @@ -127,4 +127,4 @@ class timing_graph(): def __str__(self): """ override print function output """ - return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) + return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph) diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 16d2bb4e..b4b0ef72 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -12,6 +12,7 @@ import os from globals import OPTS import tech + class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): """ Design Class for all modules to inherit the base features. @@ -137,12 +138,16 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): os.remove(tempgds) def init_graph_params(self): - """Initializes parameters relevant to the graph creation""" + """ + 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.""" + """ + Recursively create graph from instances in module. + """ # Translate port names to external nets if len(port_nets) != len(self.pins): @@ -159,7 +164,9 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): 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.""" + """ + 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, @@ -178,7 +185,9 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): subinst.mod.build_names(name_dict, subinst_name, subinst_ports) def translate_nets(self, subinst_ports, port_dict, inst_name): - """Converts connection names to their spice hierarchy equivalent""" + """ + Converts connection names to their spice hierarchy equivalent + """ converted_conns = [] for conn in subinst_ports: if conn in port_dict: @@ -188,8 +197,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): 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.""" + """ + 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)} diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index cfb9f9fa..08e2b474 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -500,8 +500,9 @@ class spice(): return power_data(dynamic, leakage) 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). + """ + 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 not exclusion_set: exclusion_set = set() @@ -520,7 +521,9 @@ class spice(): 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).""" + """ + 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 @@ -540,7 +543,9 @@ class spice(): return False def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod): - """Utility function for checking single net alias.""" + """ + Utility function for checking single net alias. + """ return self == mod and \ child_net.lower() == alias_net.lower() and \ parent_net.lower() == alias_net.lower() diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index f81e8fa8..3f3aeee4 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,copy +import shutil import debug import tech import math @@ -14,13 +14,10 @@ from .trim_spice import * from .charutils import * from .sram_op import * from .bit_polarity import * -import utils from globals import OPTS from .simulation import simulation from .measurements import * -import logical_effort -import graph_util -from sram_factory import factory + class delay(simulation): """ @@ -50,7 +47,7 @@ class delay(simulation): self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 - self.set_load_slew(0,0) + self.set_load_slew(0, 0) self.set_corner(corner) self.create_signal_names() self.add_graph_exclusions() @@ -69,7 +66,7 @@ class delay(simulation): 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) + self.check_meas_names(self.read_meas_lists + self.write_meas_lists) def check_meas_names(self, measures_lists): """ @@ -80,8 +77,8 @@ class delay(simulation): 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)) + 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): @@ -89,7 +86,7 @@ class delay(simulation): 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 + targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit self.delay_meas = [] self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file. @@ -217,7 +214,7 @@ class delay(simulation): meas.meta_str = cycle self.write_bit_meas[polarity].append(meas) # Dictionary values are lists, reduce to a single list of measurements - return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] + return [meas for meas_list in self.write_bit_meas.values() for meas in meas_list] def get_bit_measures(self, meas_tag, probe_address, probe_data): """ @@ -231,9 +228,9 @@ class delay(simulation): 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)) - if not OPTS.use_pex: - q_name = cell_name+'.'+str(storage_names[0]) - qbar_name = cell_name+'.'+str(storage_names[1]) + if not OPTS.use_pex: + q_name = cell_name + '.' + str(storage_names[0]) + qbar_name = cell_name + '.' + str(storage_names[1]) else: bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) @@ -243,54 +240,15 @@ class delay(simulation): # but they is enforced externally. {} added to names to differentiate between ports allow the # measurements are independent of the ports q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) - qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) + qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) - return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} + return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} - def set_load_slew(self,load,slew): + def set_load_slew(self, load, slew): """ Set the load and slew """ self.load = load self.slew = slew - - def create_graph(self): - """Creates timing graph to generate the timing paths for the SRAM output.""" - - self.sram.bank.bitcell_array.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 get_bl_name_search_exclusions(self): - """Gets the mods as a set which should be excluded while searching for name.""" - - # Exclude the RBL as it contains bitcells which are not in the main bitcell array - # so it makes the search awkward - return set(factory.get_mods(OPTS.replica_bitline)) - - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): - """ - Finds a single alias for the int_net in given paths. - More or less hits cause an error - """ - - net_found = False - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) - if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net),1) - elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net),1) - elif not net_found and len(aliases) == 1: - path_net_name = aliases[0] - net_found = True - if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net),1) - - return path_net_name def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" @@ -298,19 +256,19 @@ class delay(simulation): try: int(self.probe_address, 2) except ValueError: - debug.error("Probe Address is not of binary form: {0}".format(self.probe_address),1) + debug.error("Probe Address is not of binary form: {0}".format(self.probe_address), 1) if len(self.probe_address) != self.addr_size: - debug.error("Probe Address's number of bits does not correspond to given SRAM",1) + debug.error("Probe Address's number of bits does not correspond to given SRAM", 1) if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0: - debug.error("Given probe_data is not an integer to specify a data bit",1) + debug.error("Given probe_data is not an integer to specify a data bit", 1) # Adding port options here which the characterizer cannot handle. Some may be added later like ROM if len(self.read_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without read ports.",1) + debug.error("Characterizer does not currently support SRAMs without read ports.", 1) if len(self.write_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without write ports.",1) + debug.error("Characterizer does not currently support SRAMs without write ports.", 1) def write_generic_stimulus(self): """ Create the instance, supplies, loads, and access transistors. """ @@ -323,17 +281,16 @@ class delay(simulation): self.sf.write("\n* Instantiation of the SRAM\n") if not OPTS.use_pex: self.stim.inst_model(pins=self.pins, - model_name=self.sram.name) + model_name=self.sram.name) else: - self.stim.inst_sram_pex(pins=self.pins, - model_name=self.sram.name) + self.stim.inst_sram_pex(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): - self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port,i,self.dout_name,self.load)) + self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port, i, self.dout_name, self.load)) - def write_delay_stimulus(self): """ Creates a stimulus file for simulations to probe a bitcell at a given clock period. @@ -385,7 +342,6 @@ class delay(simulation): self.sf.close() - def write_power_stimulus(self, trim): """ Creates a stimulus file to measure leakage power only. This works on the *untrimmed netlist*. @@ -410,11 +366,11 @@ class delay(simulation): self.sf.write("\n* Generation of data and address signals\n") for write_port in self.write_ports: for i in range(self.word_size): - self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i), - v_val=0) + self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i), + v_val=0) for port in self.all_ports: for i in range(self.addr_size): - self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name,port, i), + self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name, port, i), v_val=0) # generate control signals @@ -431,7 +387,7 @@ class delay(simulation): self.write_power_measures() # run until the end of the cycle time - self.stim.write_control(2*self.period) + self.stim.write_control(2 * self.period) self.sf.close() @@ -1257,23 +1213,23 @@ class delay(simulation): debug.warning("In analytical mode, all ports have the timing of the first read port.") # Probe set to 0th bit, does not matter for analytical delay. - self.set_probe('0'*self.addr_size, 0) + self.set_probe('0' * self.addr_size, 0) self.create_graph() self.set_internal_spice_names() self.create_measurement_names() port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("clk", port), + self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, self.probe_data)) # Select the path with the bitline (bl) - bl_name,br_name = self.get_bl_name(self.graph.all_paths, port) + bl_name, br_name = self.get_bl_name(self.graph.all_paths, port) bl_path = [path for path in self.graph.all_paths if bl_name in path][0] # Set delay/power for slews and loads port_data = self.get_empty_measure_data_dict() power = self.analytical_power(slews, loads) - debug.info(1,'Slew, Load, Delay(ns), Slew(ns)') + debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)') max_delay = 0.0 for slew in slews: for load in loads: @@ -1282,41 +1238,45 @@ class delay(simulation): total_delay = self.sum_delays(path_delays) max_delay = max(max_delay, total_delay.delay) - debug.info(1,'{}, {}, {}, {}'.format(slew,load,total_delay.delay/1e3, total_delay.slew/1e3)) + debug.info(1, + '{}, {}, {}, {}'.format(slew, + load, + total_delay.delay / 1e3, + total_delay.slew / 1e3)) # Delay is only calculated on a single port and replicated for now. for port in self.all_ports: - for mname in self.delay_meas_names+self.power_meas_names: + for mname in self.delay_meas_names + self.power_meas_names: if "power" in mname: port_data[port][mname].append(power.dynamic) elif "delay" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.delay/1e3) + port_data[port][mname].append(total_delay.delay / 1e3) elif "slew" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.slew/1e3) + port_data[port][mname].append(total_delay.slew / 1e3) else: - debug.error("Measurement name not recognized: {}".format(mname),1) + debug.error("Measurement name not recognized: {}".format(mname), 1) # Estimate the period as double the delay with margin period_margin = 0.1 - sram_data = { "min_period":(max_delay/1e3)*2*period_margin, - "leakage_power": power.leakage} + sram_data = {"min_period": (max_delay / 1e3) * 2 * period_margin, + "leakage_power": power.leakage} - debug.info(2,"SRAM Data:\n{}".format(sram_data)) - debug.info(2,"Port Data:\n{}".format(port_data)) + debug.info(2, "SRAM Data:\n{}".format(sram_data)) + debug.info(2, "Port Data:\n{}".format(port_data)) - return (sram_data,port_data) + return (sram_data, port_data) def analytical_power(self, slews, loads): """Get the dynamic and leakage power from the SRAM""" # slews unused, only last load is used load = loads[-1] - power = self.sram.analytical_power(self.corner, load) + power = self.sram.analytical_power(self.corner, load) # convert from nW to mW - power.dynamic /= 1e6 + power.dynamic /= 1e6 power.leakage /= 1e6 - debug.info(1,"Dynamic Power: {0} mW".format(power.dynamic)) - debug.info(1,"Leakage Power: {0} mW".format(power.leakage)) + debug.info(1, "Dynamic Power: {0} mW".format(power.dynamic)) + debug.info(1, "Leakage Power: {0} mW".format(power.leakage)) return power def gen_data(self): @@ -1324,7 +1284,7 @@ class delay(simulation): for write_port in self.write_ports: for i in range(self.word_size): - sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i) + sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i) self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[write_port][i], self.period, self.slew, 0.05) def gen_addr(self): @@ -1335,7 +1295,7 @@ class delay(simulation): for port in self.all_ports: for i in range(self.addr_size): - sig_name = "{0}{1}_{2}".format(self.addr_name,port,i) + sig_name = "{0}{1}_{2}".format(self.addr_name, port, i) self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05) def gen_control(self): @@ -1346,11 +1306,10 @@ class delay(simulation): if port in self.readwrite_ports: self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) - def get_empty_measure_data_dict(self): """Make a dict of lists for each type of delay and power measurement to append results to""" - measure_names = self.delay_meas_names + self.power_meas_names + measure_names = self.delay_meas_names + self.power_meas_names # Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. - measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports] + measure_data = [{mname: [] for mname in measure_names} for i in self.all_ports] return measure_data diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index bb9f990d..ed2baeec 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -13,9 +13,6 @@ from .stimuli import * from .charutils import * from globals import OPTS from .simulation import simulation -# from .delay import delay -import graph_util -from sram_factory import factory class functional(simulation): @@ -39,7 +36,7 @@ class functional(simulation): if not self.num_spare_cols: self.num_spare_cols = 0 - self.probe_address, self.probe_data = '0'*self.addr_size,0 + self.probe_address, self.probe_data = '0' * self.addr_size, 0 self.set_corner(corner) self.set_spice_constants() self.set_stimulus_variables() @@ -49,7 +46,7 @@ class functional(simulation): self.add_graph_exclusions() self.create_graph() self.set_internal_spice_names() - self.q_name, self.qbar_name = self.get_bit_name() + self.q_name, self.qbar_name = self.get_bit_name() debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) # Number of checks can be changed @@ -60,7 +57,7 @@ class functional(simulation): self.read_results = [] def run(self, feasible_period=None): - if feasible_period: #period defaults to tech.py feasible period otherwise. + if feasible_period: # period defaults to tech.py feasible period otherwise. self.period = feasible_period # Generate a random sequence of reads and writes self.create_random_memory_sequence() @@ -249,11 +246,12 @@ class functional(simulation): def check_stim_results(self): for i in range(len(self.read_check)): if self.read_check[i][0] != self.read_results[i][0]: - error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_results[i][1], - self.read_results[i][0], - self.read_check[i][0], - int((self.read_results[i][2]-self.period)/self.period), - self.read_results[i][2]) + str = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n" + error = str.format(self.read_results[i][1], + self.read_results[i][0], + self.read_check[i][0], + int((self.read_results[i][2] - self.period) / self.period), + self.read_results[i][2]) return(0, error) return(1, "SUCCESS") @@ -423,19 +421,6 @@ class functional(simulation): self.stim.write_control(self.cycle_times[-1] + self.period) self.sf.close() - - # FIXME: refactor to share with delay.py - def create_graph(self): - """Creates timing graph to generate the timing paths for the SRAM output.""" - - self.sram.bank.bitcell_array.init_graph_params() # Removes previous bit exclusions - # Does wordline=0 and column=0 just for debug names - self.sram.bank.bitcell_array.graph_exclude_bits(0, 0) - - # Generate new graph every analysis as edges might change depending on test bit - self.graph = graph_util.timing_graph() - self.sram_spc_name = "X{}".format(self.sram.name) - self.sram.build_graph(self.graph, self.sram_spc_name, self.pins) #FIXME: Similar function to delay.py, refactor this def get_bit_name(self): @@ -449,31 +434,4 @@ class functional(simulation): return (q_name, qbar_name) - def get_bl_name_search_exclusions(self): - """Gets the mods as a set which should be excluded while searching for name.""" - - # Exclude the RBL as it contains bitcells which are not in the main bitcell array - # so it makes the search awkward - return set(factory.get_mods(OPTS.replica_bitline)) - - def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None): - """ - Finds a single alias for the int_net in given paths. - More or less hits cause an error - """ - - net_found = False - for path in paths: - aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set) - if net_found and len(aliases) >= 1: - debug.error('Found multiple paths with {} net.'.format(int_net), 1) - elif len(aliases) > 1: - debug.error('Found multiple {} nets in single path.'.format(int_net), 1) - elif not net_found and len(aliases) == 1: - path_net_name = aliases[0] - net_found = True - if not net_found: - debug.error("Could not find {} net in timing paths.".format(int_net), 1) - - return path_net_name diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index b6774e99..51f99c03 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -5,17 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil -from design import design import debug import math import tech -from .stimuli import * -from .trim_spice import * -from .charutils import * -import utils from globals import OPTS from sram_factory import factory +import graph_util + class simulation(): @@ -39,11 +35,11 @@ class simulation(): self.write_ports = self.sram.write_ports self.words_per_row = self.sram.words_per_row if self.write_size: - self.num_wmasks = int(math.ceil(self.word_size/self.write_size)) + self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) else: self.num_wmasks = 0 - def set_corner(self,corner): + def set_corner(self, corner): """ Set the corner values """ self.corner = corner (self.process, self.vdd_voltage, self.temperature) = corner @@ -51,8 +47,8 @@ class simulation(): def set_spice_constants(self): """ sets feasible timing parameters """ self.period = tech.spice["feasible_period"] - self.slew = tech.spice["rise_time"]*2 - self.load = tech.spice["dff_in_cap"]*4 + self.slew = tech.spice["rise_time"] * 2 + self.load = tech.spice["dff_in_cap"] * 4 self.v_high = self.vdd_voltage - tech.spice["nom_threshold"] self.v_low = tech.spice["nom_threshold"] @@ -79,20 +75,20 @@ class simulation(): self.t_current = 0 # control signals: only one cs_b for entire multiported sram, one we_b for each write port - self.csb_values = {port:[] for port in self.all_ports} - self.web_values = {port:[] for port in self.readwrite_ports} + self.csb_values = {port: [] for port in self.all_ports} + self.web_values = {port: [] for port in self.readwrite_ports} # Raw values added as a bit vector - self.addr_value = {port:[] for port in self.all_ports} - self.data_value = {port:[] for port in self.write_ports} - self.wmask_value = {port:[] for port in self.write_ports} - self.spare_wen_value = {port:[] for port in self.write_ports} + self.addr_value = {port: [] for port in self.all_ports} + self.data_value = {port: [] for port in self.write_ports} + self.wmask_value = {port: [] for port in self.write_ports} + self.spare_wen_value = {port: [] for port in self.write_ports} # Three dimensional list to handle each addr and data bits for each port over the number of checks - self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} - self.data_values = {port:[[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} - self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} - self.spare_wen_values = {port:[[] for bit in range(self.num_spare_cols)] for port in self.write_ports} + self.addr_values = {port: [[] for bit in range(self.addr_size)] for port in self.all_ports} + self.data_values = {port: [[] for bit in range(self.word_size + self.num_spare_cols)] for port in self.write_ports} + self.wmask_values = {port: [[] for bit in range(self.num_wmasks)] for port in self.write_ports} + self.spare_wen_values = {port: [[] for bit in range(self.num_spare_cols)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -109,7 +105,7 @@ class simulation(): csb_val = 0 web_val = 0 elif op != "noop": - debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1) + debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port, op), 1) # Append the values depending on the type of port self.csb_values[port].append(csb_val) @@ -129,7 +125,7 @@ class simulation(): elif c=="1": self.data_values[port][bit].append(1) else: - debug.error("Non-binary data string",1) + debug.error("Non-binary data string", 1) bit -= 1 def add_address(self, address, port): @@ -142,12 +138,11 @@ class simulation(): if c=="0": self.addr_values[port][bit].append(0) elif c=="1": - self.addr_values[port][bit].append(1) + self.addr_values[port][bit].append(1) else: - debug.error("Non-binary address string",1) + debug.error("Non-binary address string", 1) bit -= 1 - def add_wmask(self, wmask, port): """ Add the array of address values """ debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.") @@ -191,9 +186,9 @@ class simulation(): self.t_current += self.period self.add_control_one_port(port, "write") - self.add_data(data,port) - self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_data(data, port) + self.add_address(address, port) + self.add_wmask(wmask, port) self.add_spare_wen("1" * self.num_spare_cols, port) #Add noops to all other ports. @@ -221,11 +216,11 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) + self.add_wmask("0" * self.num_wmasks, port) self.add_spare_wen("0" * self.num_spare_cols, port) #Add noops to all other ports. @@ -276,12 +271,12 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) - self.add_spare_wen("0" * self.num_spare_cols, port) + self.add_wmask("0" * self.num_wmasks, port) + self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ @@ -290,7 +285,7 @@ class simulation(): try: self.add_address(self.addr_value[port][-1], port) except: - self.add_address("0"*self.addr_size, port) + self.add_address("0" * self.addr_size, port) # If the port is also a readwrite then add # the same value as previous cycle @@ -298,11 +293,11 @@ class simulation(): try: self.add_data(self.data_value[port][-1], port) except: - self.add_data("0"*(self.word_size + self.num_spare_cols), port) + self.add_data("0" * (self.word_size + self.num_spare_cols), port) try: self.add_wmask(self.wmask_value[port][-1], port) except: - self.add_wmask("0"*self.num_wmasks, port) + self.add_wmask("0" * self.num_wmasks, port) self.add_spare_wen("0" * self.num_spare_cols, port) def add_noop_clock_one_port(self, port): @@ -321,23 +316,23 @@ class simulation(): if unselected_port != port: self.add_noop_one_port(unselected_port) - def append_cycle_comment(self, port, comment): """Add comment to list to be printed in stimulus file""" #Clean up time before appending. Make spacing dynamic as well. time = "{0:.2f} ns:".format(self.t_current) - time_spacing = len(time)+6 + time_spacing = len(time) + 6 self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times), port, time, time_spacing, - comment)) + comment)) def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): if op == "noop": - comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period), - t_current, - t_current+self.period) + str = "\tIdle during cycle {0} ({1}ns - {2}ns)" + comment = str.format(int(t_current / self.period), + t_current, + t_current + self.period) elif op == "write": comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, addr, @@ -346,40 +341,41 @@ class simulation(): t_current, t_current+self.period) elif op == "partial_write": - comment = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)".format(word, - addr, - wmask, - port, - int(t_current / self.period), - t_current, - t_current + self.period) + str = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)" + comment = str.format(word, + addr, + wmask, + port, + int(t_current / self.period), + t_current, + t_current + self.period) else: - comment = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, - addr, - port, - int(t_current/self.period), - t_current, - t_current+self.period) - + str = "\tReading {0} from address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)" + comment = str.format(word, + addr, + port, + int(t_current / self.period), + t_current, + t_current + self.period) return comment def gen_pin_names(self, port_signal_names, port_info, abits, dbits): """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. + # 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)) + 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)) + pin_names.append("{0}{1}_{2}".format(addr_name, port, i)) #Control signals not finalized. for port in range(total_ports): @@ -394,16 +390,16 @@ class simulation(): if self.write_size: for port in write_index: for bit in range(self.num_wmasks): - pin_names.append("WMASK{0}_{1}".format(port,bit)) + pin_names.append("WMASK{0}_{1}".format(port, bit)) if self.num_spare_cols: for port in write_index: for bit in range(self.num_spare_cols): - pin_names.append("SPARE_WEN{0}_{1}".format(port,bit)) + pin_names.append("SPARE_WEN{0}_{1}".format(port, bit)) 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}{1}_{2}".format(dout_name, read_output, i)) pin_names.append("{0}".format("vdd")) pin_names.append("{0}".format("gnd")) @@ -425,8 +421,8 @@ class simulation(): port = self.read_ports[0] if not OPTS.use_pex: - self.graph.get_all_paths('{}{}'.format("clk", port), - '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + self.graph.get_all_paths('{}{}'.format("clk", port), + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) sen_with_port = self.get_sen_name(self.graph.all_paths) if sen_with_port.endswith(str(port)): @@ -435,38 +431,38 @@ class simulation(): self.sen_name = sen_with_port debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") - debug.info(2,"s_en name = {}".format(self.sen_name)) + debug.info(2, "s_en name = {}".format(self.sen_name)) bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port) - port_pos = -1-len(str(self.probe_data))-len(str(port)) + port_pos = -1 - len(str(self.probe_data)) - len(str(port)) - if bl_name_port.endswith(str(port)+"_"+str(self.probe_data)): - self.bl_name = bl_name_port[:port_pos] +"{}"+ bl_name_port[port_pos+len(str(port)):] + if bl_name_port.endswith(str(port) + "_" + str(self.probe_data)): + self.bl_name = bl_name_port[:port_pos] + "{}" + bl_name_port[port_pos + len(str(port)):] elif not bl_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 self.bl_name = bl_name_port else: self.bl_name = bl_name_port debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") - if br_name_port.endswith(str(port)+"_"+str(self.probe_data)): - self.br_name = br_name_port[:port_pos] +"{}"+ br_name_port[port_pos+len(str(port)):] + if br_name_port.endswith(str(port) + "_" + str(self.probe_data)): + self.br_name = br_name_port[:port_pos] + "{}" + br_name_port[port_pos + len(str(port)):] elif not br_name_port[port_pos].isdigit(): # single port SRAM case, bl will not be numbered eg bl_0 self.br_name = br_name_port else: - self.br_name = br_name_port + self.br_name = br_name_port debug.warning("Error occurred while determining bitline names. Can cause faults in simulation.") - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) else: - self.graph.get_all_paths('{}{}'.format("clk", port), - '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + self.graph.get_all_paths('{}{}'.format("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.sen_name = self.get_sen_name(self.graph.all_paths) + debug.info(2, "s_en name = {}".format(self.sen_name)) - self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size-1) - self.br_name = "br{0}_{1}".format(port, OPTS.word_size-1) - debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name)) + self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1) + self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1) + debug.info(2, "bl name={}, br name={}".format(self.bl_name, self.br_name)) def get_sen_name(self, paths, assumed_port=None): """ @@ -482,17 +478,53 @@ class simulation(): sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) if OPTS.use_pex: sen_name = sen_name.split('.')[-1] - return sen_name + return sen_name + def create_graph(self): + """Creates timing graph to generate the timing paths for the SRAM output.""" + self.sram.clear_exclude_bits() # Removes previous bit exclusions + self.sram.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_instance_name = "X{}".format(self.sram.name) + self.sram.build_graph(self.graph, self.sram_instance_name, self.pins) + + def get_bl_name_search_exclusions(self): + """Gets the mods as a set which should be excluded while searching for name.""" + + # Exclude the RBL as it contains bitcells which are not in the main bitcell array + # so it makes the search awkward + return set(factory.get_mods(OPTS.replica_bitline)) + + def get_alias_in_path(self, paths, internal_net, mod, exclusion_set=None): + """ + Finds a single alias for the internal_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_instance_name, self.pins, path, internal_net, mod, exclusion_set) + if net_found and len(aliases) >= 1: + debug.error('Found multiple paths with {} net.'.format(internal_net), 1) + elif len(aliases) > 1: + debug.error('Found multiple {} nets in single path.'.format(internal_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(internal_net), 1) + + return path_net_name + def get_bl_name(self, paths, port): """Gets the signal name associated with the bitlines in the bank.""" - cell_mod = factory.create(module_type=OPTS.bitcell) + cell_mod = factory.create(module_type=OPTS.bitcell) cell_bl = cell_mod.get_bl_name(port) cell_br = cell_mod.get_br_name(port) - bl_found = False # Only a single path should contain a single s_en name. Anything else is an error. bl_names = [] exclude_set = self.get_bl_name_search_exclusions() @@ -501,4 +533,7 @@ class simulation(): if OPTS.use_pex: for i in range(len(bl_names)): bl_names[i] = bl_names[i].split('.')[-1] - return bl_names[0], bl_names[1] \ No newline at end of file + return bl_names[0], bl_names[1] + + + diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 5dfaa007..2371a609 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1076,14 +1076,30 @@ class bank(design.design): offset=control_pos) def graph_exclude_precharge(self): - """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" + """ + Precharge adds a loop between bitlines, can be excluded to reduce complexity + """ for port in self.read_ports: if self.port_data[port]: self.port_data[port].graph_exclude_precharge() def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" + """ + 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) + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + self.bitcell_array.graph_exclude_bits(targ_row, targ_col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.clear_exclude_bits() + diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 111f2795..6ce7a7da 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -105,7 +105,9 @@ class bitcell_array(bitcell_base_array): return bl_wire def graph_exclude_bits(self, targ_row, targ_col): - """Excludes bits in column from being added to graph except target""" + """ + 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): diff --git a/compiler/modules/global_bitcell_array.py b/compiler/modules/global_bitcell_array.py index a6d079aa..db6f5f26 100644 --- a/compiler/modules/global_bitcell_array.py +++ b/compiler/modules/global_bitcell_array.py @@ -297,6 +297,15 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array): for i, local_col in enumerate(self.col_offsets): if local_col > col: break + else: + # In this case, we it should be in the last bitcell array + i = len(self.col_offsets) return self.local_mods[i - 1].get_cell_name(inst_name + '.x' + self.local_insts[i - 1].name, row, col) + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + for mod in self.local_mods: + mod.clear_exclude_bits() diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index 840178a6..633478c1 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -279,4 +279,10 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) - + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.clear_exclude_bits() + diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 4e1c4df1..5dffd116 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -529,15 +529,27 @@ class replica_bitcell_array(bitcell_base_array.bitcell_base_array): return bl_wire def graph_exclude_bits(self, targ_row, targ_col): - """Excludes bits in column from being added to graph except target""" + """ + Excludes bits in column from being added to graph except target + """ self.bitcell_array.graph_exclude_bits(targ_row, targ_col) def graph_exclude_replica_col_bits(self): - """Exclude all replica/dummy cells in the replica columns except the replica bit.""" + """ + Exclude all replica/dummy cells in the replica columns except the replica bit. + """ for port in self.left_rbl + self.right_rbl: self.replica_columns[port].exclude_all_but_replica() def get_cell_name(self, inst_name, row, col): - """Gets the spice name of the target bitcell.""" + """ + Gets the spice name of the target bitcell. + """ return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bitcell_array.init_graph_params() diff --git a/compiler/modules/replica_column.py b/compiler/modules/replica_column.py index e7b938e7..dafe51e6 100644 --- a/compiler/modules/replica_column.py +++ b/compiler/modules/replica_column.py @@ -200,8 +200,10 @@ class replica_column(bitcell_base_array): return self.bitline_names[port] def get_bitcell_pins(self, row, col): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = [] for port in self.all_ports: bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))]) @@ -212,8 +214,10 @@ class replica_column(bitcell_base_array): return bitcell_pins def get_bitcell_pins_col_cap(self, row, col): - """ Creates a list of connections in the bitcell, - indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = [] for port in self.all_ports: bitcell_pins.extend([x for x in self.get_bitline_names(port) if x.endswith("_{0}".format(col))]) @@ -223,7 +227,9 @@ class replica_column(bitcell_base_array): return bitcell_pins def exclude_all_but_replica(self): - """Excludes all bits except the replica cell (self.replica_bit).""" + """ + Excludes all bits except the replica cell (self.replica_bit). + """ for row, cell in self.cell_inst.items(): if row != self.replica_bit: diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 55e2de49..d18e1366 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -630,4 +630,15 @@ class sram_base(design, verilog, lef): def lvs_write(self, sp_name): self.sp_write(sp_name, lvs_netlist=True) - \ No newline at end of file + + def graph_exclude_bits(self, targ_row, targ_col): + """ + Excludes bits in column from being added to graph except target + """ + self.bank.graph_exclude_bits(targ_row, targ_col) + + def clear_exclude_bits(self): + """ + Clears the bit exclusions + """ + self.bank.clear_exclude_bits()