############################################## # DiffLengthCompare.py is the "Differential Trace Length Compare Utility" # It is used to compare the length of the two traces forming differential trace pairs. # The program compares the traces of a specified list of traces given by their net name. # It reads a Mentor Trace Length file and relies on our naming convention to recognize # the direct trace (ending in _DIR) and complementary trace (ending in _CMP) # # Command Line Parameters: # # -c configuration_file_name # This provide the name of the configuration file to read as input. # This argument is optional and the default input file name is "DiffLengthCompare.cfg" # # -t trace_length_file_name # This provide the name of the Mentor trace length file to be parsed # This name may also be given in the configuration file # A name given in the configuration file overrides a name given on the command line # # Configuration File : # # One file is read as input to ingest the configuration data. # The default configuration file name is "DiffLengthCompare.cfg" # An alternate configuration file name may be given via the command line. # # This input file is executed on the fly as python commands. # Any python syntax can be parsed but this is simply used to initialize variables. # The name of each variable should start in the first column. # # This configuration file is expected to initialize the variables: # # - "trace_length_file_name" # This is the name (including path if needed) of the Mentor Trace Length file to be analysed. # This Trace Length file name may alternatively be given on the command line. # A name given in the configuration file overrides any name given on the command line # # - "diff_trace_list" # This is how the list of DIFFERENTIAL trace net names is specified to the program. # DiffLengthCompare.py will locate the two traces forming each named differential pair # and compare the lengths of these two traces. # Each entry in the "diff_trace_list" (outer) list of trace names is also an (inner) list. # Each entry can be a list with just one element or a list with mulitple related differential traces # to be added together before comparing. # The name of each differential trace is given by its prefix, i.e. without the _DIR or _CMP # e.g. diff_trace_list = ( # ( "GTH_FO_CH_62_IN", ), # ( "GTH_FO_CH_6_OCP_HUB", "GTH_FO_CH_6_OUT_HUB" ), # ) # # - "show_dir_and_cmp_lengths" # When this variable is set to 1 the utility will also show # the _DIR and _CMP lengths for each path compared # This variable is optional and the default is to not show the trace lengths. # # - "flag_trace_length_le_than" # When this variable is set to a positive number (in mm) the program will report # a wardning for all traces with a length equal or shorter than that value. # A value of "0" mm will report all traces of null lengths. # All traces from the Mentor Trace Length File are checked, differential or not. # Trace names starting with "No_Conn" (non-case-sensitive) are ignored. # When the value is negative the program does not report short traces. # This variable is optional and the default is to not report short traces. # # Output File: # # One logfile DiffLengthCompare.log is created in the working directory # The logfile includes a time stamp and the result of the length comparison. # # Output to screen: # # All program activity messages and the results of the length comparisons # are printed to the screen and copied to the logfile # ############################################## # Version 1.0 08-Aug-2016: Initial version # Version 2.0 12-Aug-2016: Add reporting on short trace anywhere # Add reporting of longest DIR-CMP mismatch # Version 2.1 03-Jan-2017: Delay opening the logfile, add timestamp and config file name to logfile name ############################################## import string import sys import time ############################################## app_title = "DiffLengthCompare" app_version = "2.1" ############################################## # provide default values for the configuration options ############################################# # default configuration file name # this name may be overridden on the command line (with -c other_cfg_file_name) default_config_file_name = "DiffLengthCompare.cfg" # "trace_length_filename" is the name of the Mentor trace length file to be parsed # This name is typically overridden in the configuration file # This name may instead be overridden on the command line (with -t trace_file_name) # A name given in the configuration file overrides any name given on the command line trace_length_filename = "none_yet_given" # "diff_trace_list" is the list of DIFFERENTIAL trace net names. # DiffLengthCompare.py will locate the two traces from each named differential pair # and compare the lengths of these two traces. # # Each entry in the "diff_trace_list" (outer) list of trace names is also an (inner) list. # Each of these entries can be a list with just one differential trace # or a list with mulitple related differential traces to be added together before comparing. diff_trace_list = ( ) # optionally we can also show the _DIR and _CMP lengths for each path compared show_dir_and_cmp_lengths = 0 # report on any trace found with zero or short length (unless name starts with "No_Conn" non-case-sensitive) # set negative value to skip checking flag_trace_length_le_than = -1 ############################################## # cosmetic names to help readability ############################################## str_dir = "_DIR" str_cmp = "_CMP" str_no_conn = "NO_CONN_" # index of elements in trace length line : net name and trace length idx_trace_name = 0 idx_trace_length = 1 ############################################## import string import sys import time ################################################################### ################################################################### def show_exc_info ( logfile = None, stop_on_except = 0 ) : #---------------------------- exception_info = sys.exc_info() # retrieve exception info exception_type = exception_info[0] # exception type exception_value = exception_info[1] # exception value exception_at_line = exception_info[2].tb_lineno # traceback item print ' *** exception_type :', exception_type print ' *** exception_value :', exception_value print ' *** exception_at_line :', exception_at_line if ( logfile != None ): logfile.flush ( ) logfile.write ( ' *** exception_type :' + str(exception_type) + '\n' ) logfile.flush ( ) logfile.write ( ' *** exception_value :' + str(exception_value) + '\n' ) logfile.flush ( ) logfile.write ( ' *** exception_at_line :' + str(exception_at_line) + '\n' ) logfile.flush ( ) # del statement is executed left to right # We need to drop reference to traceback # This must be done explicitely (as below) when directly inside except clause # but it would be done implictely when returning from a function like here del [ exception_at_line, exception_value, exception_type, exception_info ] if stop_on_except : raise # stop and get a trace ################################################################### # read and parse command line arguments #-------------------------------- # There is a default config file name which may be overriden via command line arguments # but we need to notice when it is overriden config_file_name = default_config_file_name command_line_argument = sys.argv[1:] cmdarg_tot = len ( command_line_argument ) cmdarg_num = 0 while ( cmdarg_num < cmdarg_tot ) : # check for known argument switch and that there will always be a filename after the switch if ( ( command_line_argument[cmdarg_num] not in ( "-c", "-t" ) ) or ( cmdarg_num+1 >= cmdarg_tot ) ) : status = "Command line error at argument <%s>" % command_line_argument[cmdarg_num] print status app_logfile.write ( "\n%s\n" % status ) print """ Usage: python DiffLengthCompare [-c input_config_filename] [-t trace_length_filename] \n """ sys.exit( 1 ) # input net list file name elif ( command_line_argument[cmdarg_num] == "-c" ) : config_file_name = command_line_argument[cmdarg_num+1] # input net list file name elif ( command_line_argument[cmdarg_num] == "-t" ) : trace_length_filename = command_line_argument[cmdarg_num+1] cmdarg_num = cmdarg_num + 2 #open a logfile #-------------------------------- current_time_bin = time.localtime( time.time() ) current_time_asc = time.asctime ( current_time_bin ) # start logfile name with app name logfile_name = app_title # if not the default add configuration file name (without extension, if present) if ( config_file_name != default_config_file_name ) : logfile_name = "%s_%s" % ( logfile_name, string.split( config_file_name, '.' )[0] ) # add time stamp and extension logfile_name = "%s_%s.log" % ( logfile_name, time.strftime ( "%Y-%m-%d_%H-%M", # e.g. '2017-01-03_13-09' current_time_bin ) ) # logfile name is complete app_logfile = open ( logfile_name, 'w' ) #say hello #-------------------------------- print " --------------------------" print " %s V%s" % ( app_title, app_version ) print " %s" % current_time_asc print " --------------------------" print "" app_logfile.write ( " --------------------------\n" ) app_logfile.write ( " %s V%s \n" % ( app_title, app_version ) ) app_logfile.write ( " %s \n" % current_time_asc ) app_logfile.write ( " --------------------------\n" ) app_logfile.write ( "\n" ) print "Screen output copied to Logfile <%s>" % logfile_name app_logfile.write ( "Screen output copied to Logfile <%s>\n" % logfile_name ) # ingest the config file #-------------------------------- try: status = "Executing Configuration File <%s>" % config_file_name print status app_logfile.write ( "\n%s\n" % status ) execfile ( config_file_name ) status = "Found Name of Trace Length File <%s>" % trace_length_filename print status app_logfile.write ( "%s\n" % status ) status = "Found Net Names for %d Differential Trace Paths to Compare" % len(diff_trace_list) print status app_logfile.write ( "%s\n" % status ) if ( flag_trace_length_le_than >= 0.0 ) : status = "Will flag any trace shorter than %3.2f mm" % flag_trace_length_le_than else : status = "Will not flag short traces" print status app_logfile.write ( "%s\n" % status ) except: status = "Error Reading Config File" print status app_logfile.write ( "%s\n" % status ) show_exc_info ( app_logfile, stop_on_except = 1 ) # everything now ready to go #------------------------------ # initialize trace net name dictionary trace_length_dict = dict () # will be filled with all trace names found # open input trace length file status = "\nReading the Trace Length File" print status app_logfile.write ( "%s\n" % status ) inp_trace_length_file = open ( trace_length_filename, "r" ) # read trace length input file, but skip first line line_num = 1 for trace_length_line in inp_trace_length_file.readlines()[1:] : line_num += 1 try : length_line_items = string.split ( trace_length_line ) #print trace_length_line, length_line_items if ( len( length_line_items ) != 2 ) : status = "ERROR: unexpected format at line #%d : \n<%s>" % ( line_num, trace_length_line[:-1] ) print status app_logfile.write ( "%s\n" % status ) break elif trace_length_dict.has_key( length_line_items[idx_trace_name] ) : status = "ERROR: duplicate net name found at line #%d : \n<%s>" % ( line_num, trace_length_line[:-1] ) print status app_logfile.write ( "%s\n" % status ) break trace_name = length_line_items[idx_trace_name] trace_length = eval ( length_line_items[idx_trace_length] ) except : status = "ERROR: parsing problem at line #%d : \n<%s>" % ( line_num, trace_length_line[:-1] ) print status app_logfile.write ( "%s\n" % status ) show_exc_info ( app_logfile, stop_on_except = 1 ) # add trace to dictionary trace_length_dict[ trace_name ] = trace_length # watch for short traces if ( ( trace_length <= flag_trace_length_le_than ) and ( trace_name.upper().find( str_no_conn ) != 0 ) ) : # traces that start with "NO_CONN_ are ok status = "WARNING: trace <%s> read as length %3.2f mm" % ( trace_name, trace_length ) print status app_logfile.write ( "%s\n" % status ) # done with trace length file #-------------------------- inp_trace_length_file.close() # now compute and compare requested differential trace lengths #------------------------------------------------------------- status = "\nComputing and comparing differential trace path lengths" print status app_logfile.write ( "\n%s\n" % status ) tot_paths = 0 largest_mismatch_trace_length = 0.0 largest_mismatch_trace_name = "" # scan all trace entries for trace_entry in diff_trace_list : tot_paths += 1 # we will need to compute the length of the _DIR and _CMP paths trace_length_dir = 0.0 trace_length_cmp = 0.0 # we will need to prepare a string of segment names trace_segment_names = "" # add all the segments for trace_segment_entry in trace_entry : # make sure the _DIR half of the diff pair was in the trace length file trace_name_dir = "%s%s" % ( trace_segment_entry, str_dir ) if ( not trace_length_dict.has_key( trace_name_dir ) ) : status = "ERROR: net name not found <%s>" % trace_name_dir print status app_logfile.write ( "%s\n" % status ) sys.exit(1) # make sure the _CMP half of the diff pair was in the trace length file trace_name_cmp = "%s%s" % ( trace_segment_entry, str_cmp ) if ( not trace_length_dict.has_key( trace_name_cmp ) ) : status = "ERROR: net name not found <%s>" % trace_name_cmp print status app_logfile.write ( "%s\n" % status ) sys.exit(1) # keep track of all the segment names added if ( len ( trace_segment_names ) != 0 ) : # add "plus" if not first segment trace_segment_names = "%s Plus %s" % ( trace_segment_names, trace_segment_entry ) else : trace_segment_names = trace_segment_entry # add contribution of this segment try : trace_length_dir += trace_length_dict[ trace_name_dir ] except : status = "ERROR: error parsing length for <%s> which had been read in as <%s>" % ( trace_name_dir, trace_length_dict[ trace_name_dir ] ) print status app_logfile.write ( "%s\n" % status ) try : trace_length_cmp += trace_length_dict[ trace_name_cmp ] except : status = "ERROR: error parsing length for <%s> which had been read in as <%s>" % ( trace_name_cmp, trace_length_dict[ trace_name_cmp ] ) print status app_logfile.write ( "%s\n" % status ) # now we can show what was compared, e.g. : # Compared: GTH_FO_CH_6_OCP_HUB Plus GTH_FO_CH_6_OUT_HUB status = "\nCompared: %s " % trace_segment_names print status app_logfile.write ( "%s\n" % status ) # and we can show the trace lengths e.g.: # Length: DIR is 150.25 mm and CMP is 150.12 mm if show_dir_and_cmp_lengths : status = " Length: DIR is %6.2f mm and CMP is %6.2f mm" % ( trace_length_dir, trace_length_cmp ) print status app_logfile.write ( "%s\n" % status ) # and we can show the comparison result e.g.: # Result: CMP is short by 1.37 mm length_dir_minus_cmp = trace_length_dir - trace_length_cmp if length_dir_minus_cmp == 0.0 : status = " Result: CMP and DIR match" elif length_dir_minus_cmp > 0 : status = " Result: CMP is short by %5.2f mm" % + length_dir_minus_cmp else : status = " Result: DIR is short by %5.2f mm" % - length_dir_minus_cmp print status app_logfile.write ( "%s\n" % status ) # and we keep track of the longest mismatch if ( abs( length_dir_minus_cmp ) >= largest_mismatch_trace_length ) : largest_mismatch_trace_length = abs( length_dir_minus_cmp ) largest_mismatch_trace_name = trace_segment_names # Done #-------------------------------- # write total number of traces status = "\nThe total number of differential paths compared was %d" % tot_paths print status app_logfile.write ( "%s\n" % status ) status = "The largest mismatch found was %3.2f mm for <%s>" % ( largest_mismatch_trace_length, largest_mismatch_trace_name ) print status app_logfile.write ( "%s\n" % status ) status = "Done\n" print status app_logfile.write ( "\n%s\n" % status )