diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index d2759930..25c79d3b 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -1,4 +1,4 @@ -import os +import os, copy from collections import defaultdict import gdsMill @@ -16,6 +16,7 @@ class timing_graph(): 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.""" @@ -34,7 +35,7 @@ class timing_graph(): node = node.lower() self.graph[node] = set() - def print_all_paths(self, src_node, dest_node, rmv_rail_nodes=True): + 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() @@ -51,13 +52,15 @@ class timing_graph(): # Create an array to store paths path = [] - self.path_count = 0 + self.all_paths = [] # Call the recursive helper function to print all paths - self.print_all_paths_util(src_node, dest_node, visited, path) - debug.info(1, "Paths found={}".format(self.path_count)) + self.get_all_paths_util(src_node, dest_node, visited, path) + debug.info(1, "Paths found={}".format(len(self.all_paths))) - def print_all_paths_util(self, cur_node, dest_node, visited, path): + 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) @@ -67,18 +70,32 @@ class timing_graph(): # current path[] if cur_node == dest_node: debug.info(1,"{}".format(path)) - self.path_count+=1 + 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.print_all_paths_util(node, dest_node, visited, path) + 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 get_path_preconvergence_point(self, path1, path2): + """Assuming the inputs paths have the same starting point and end point, the + paths should split and converge at some point before/at the last stage. Finds the + point before convergence.""" + debug.check(path1[0] == path2[0], "Paths must start from the same point.") + debug.check(path1[-1] == path2[-1], "Paths must end from the same point.") + #Paths must end at the same point, so the paths are traversed backwards to find + #point of convergence. There could be multiple points, only finds first. + for point1,point2 in zip(reversed(path1), reversed(path2)): + if point1 != point2: + return (point1,point2) + debug.info(1,"Pre-convergence point not found, paths are equals.") + return path1[0],path2[0] + 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_spice.py b/compiler/base/hierarchy_spice.py index f2535f86..6dddf4c5 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -140,7 +140,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 diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 3939511f..b7169a0d 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -81,49 +81,39 @@ class delay(simulation): if obj.meta_str is "read0": obj.meta_add_delay = True - 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 = "{}" + # 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) + # bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + # br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) # self.read_lib_meas.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) 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_delay_measurement_objects()) + read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_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_meas = [] - 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_name", + self.bl_name)) + self.bitline_volt_meas[-1].meta_str = 'read0' - targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_meas[-1].meta_str = "read0" - # self.bitline_delay_meas.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) - # self.bitline_delay_meas[-1].meta_str = "read1" - #Enforces the time delay on the bitline measurements for read0 or read1 - for obj in self.bitline_delay_meas: - obj.meta_add_delay = True - - return self.bitline_delay_meas + self.bitline_volt_meas.append(voltage_at_measure("v_br_name", + self.br_name)) + self.bitline_volt_meas[-1].meta_str = 'read1' + return self.bitline_volt_meas def create_write_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" @@ -155,9 +145,11 @@ class delay(simulation): debug_meas.parent = meas.name self.debug_delay_meas.append(debug_meas) + #Output voltage measures self.debug_volt_meas.append(voltage_at_measure("v_{}".format(debug_meas.meta_str), debug_meas.targ_name_no_port)) self.debug_volt_meas[-1].meta_str = debug_meas.meta_str + return self.debug_delay_meas+self.debug_volt_meas def set_load_slew(self,load,slew): @@ -183,7 +175,28 @@ class delay(simulation): self.graph = graph_util.timing_graph() self.sram.build_graph(self.graph,"X{}".format(self.sram.name),self.pins) #debug.info(1,"{}".format(graph)) - #graph.print_all_paths('clk0', 'DOUT0[0]') + port = 0 + self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \ + '{}{}_{}'.format(self.dout_name, port, self.probe_data)) + sen_name = self.sram.get_sen_name(self.sram.name, port).lower() + debug.info(1, "Sen name={}".format(sen_name)) + sen_path = [] + for path in self.graph.all_paths: + if sen_name in path: + sen_path = path + break + debug.check(len(sen_path)!=0, "Could not find s_en timing path.") + preconv_names = [] + for path in self.graph.all_paths: + if not path is sen_path: + sen_preconv = self.graph.get_path_preconvergence_point(path, sen_path) + if sen_preconv not in preconv_names: + preconv_names.append(sen_preconv[0]) #only save non-sen_path names + + #Not an good way to separate inverting and non-inverting bitlines... + self.bl_name = [bl for bl in preconv_names if 'bl' in bl][0] + self.br_name = [bl for bl in preconv_names if 'br' in bl][0] + debug.info(1,"bl_name={}".format(self.bl_name)) def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" @@ -551,9 +564,6 @@ class delay(simulation): 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_lib_meas: @@ -589,7 +599,14 @@ class delay(simulation): success = False debug.info(1, "Debug measurement failed. Incorrect Value found on output.") break - + + #FIXME: these checks need to be re-done to be more robust against possible errors + bitline_results = {} + for meas in self.bitline_volt_meas: + val = meas.retrieve_measure(port=port) + bitline_results[meas.meta_str] = val + + for meas in self.debug_volt_meas: val = meas.retrieve_measure(port=port) debug.info(2,"{}={}".format(meas.name, val)) @@ -604,19 +621,20 @@ class delay(simulation): success = False debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val)) break - + + #If the bitlines have a correct value while the output does not then that is a + #sen error. FIXME: there are other checks that can be done to solidfy this conclusion. + if not success and self.check_bitline_meas(bitline_results[meas.meta_str]): + debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1) + return success - 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_meas: - 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_bitline_meas(self, v_bitline): + """Checks the value of the discharging bitline""" + + return v_bitline < self.vdd_voltage*0.9 + def run_power_simulation(self): """ diff --git a/compiler/modules/tri_gate.py b/compiler/modules/tri_gate.py index 4b7501dd..633715af 100644 --- a/compiler/modules/tri_gate.py +++ b/compiler/modules/tri_gate.py @@ -10,7 +10,7 @@ class tri_gate(design.design): netlist should be available in the technology library. """ - pin_names = ["in", "out", "en", "en_bar", "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"]) diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index ca10d827..cf822861 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -327,3 +327,21 @@ class sram_1bank(sram_base): #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.") + debug.info(1,"pins={}".format(self.pins)) + debug.info(1,"cl conns={}".format(control_conns)) + return "X{}.{}".format(sram_name, sen_name) + + diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 2cdd3414..16ef6de1 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -26,15 +26,15 @@ class timing_sram_test(openram_test): reload(characterizer) from characterizer import delay from sram_config import sram_config - c = sram_config(word_size=1, - num_words=16, - num_banks=1) - c.words_per_row=1 - # c = sram_config(word_size=32, - # num_words=256, + # c = sram_config(word_size=1, + # num_words=16, # num_banks=1) - # c.words_per_row=2 - #OPTS.use_tech_delay_chain_size = True + # 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)