diff --git a/synthDC/ppa/bestSynths.csv b/synthDC/ppa/bestSynths.csv index 885eeb962..655f171a1 100644 --- a/synthDC/ppa/bestSynths.csv +++ b/synthDC/ppa/bestSynths.csv @@ -4,10 +4,10 @@ binencoder,sky130,16,1000,1.0000,136.220003,77.243,0.021773774467348 binencoder,sky130,32,1000,1.0000,372.400007,189.626,0.04371111111111111 binencoder,sky130,64,1000,1.0000,797.720015,382.205,0.07393850658857981 binencoder,sky130,128,900,1.1111,1602.300031,610.009,0.1261366969785861 -adder,sky130,8,1000,1.0000,253.820005,154.438,0.10825587752870422 -adder,sky130,16,1000,1.0000,722.260013,485.109,0.32460910944935417 -adder,sky130,32,1000,1.0000,1440.600027,714.057,0.6580226904376014 -adder,sky130,64,1000,1.0000,2781.240054,1050.0,0.9392239364188874 +adder,sky130,8,1700,0.588235,253.820005,154.438,0.10825587752870422 +adder,sky130,16,1300,0.7692307,722.260013,485.109,0.32460910944935417 +adder,sky130,32,1100,0.90909,1440.600027,714.057,0.6580226904376014 +adder,sky130,64,950,1.0526315,2781.240054,1050.0,0.9392239364188874 adder,sky130,128,900,1.1111,6186.740118,2230.0,2.1480106100795755 csa,sky130,8,1000,1.0000,266.560005,154.202,0.13650573115665163 csa,sky130,16,1000,1.0000,533.12001,308.404,0.27263530601922104 @@ -19,11 +19,11 @@ shifter,sky130,16,1000,1.0000,666.400006,558.433,0.19552906110283155 shifter,sky130,32,1000,1.0000,1475.880027,768.262,0.3807431082700759 shifter,sky130,64,1000,1.0000,3914.120062,2680.0,1.144802541988198 shifter,sky130,128,900,1.1111,9192.400136,6080.0,2.9008914525432616 -comparator,sky130,8,1000,1.0000,200.900004,136.6,0.05001033271337053 -comparator,sky130,16,1000,1.0000,358.680007,189.253,0.06321553011448482 -comparator,sky130,32,1500,0.666666,690.900013,315.709,0.10771793448084398 -comparator,sky130,64,1300,0.7692307,1372.980026,508.393,0.2048577820389901 -comparator,sky130,128,1100,0.909090,2744.980052,796.047,0.34396273737011823 +comparator,sky130,8,1700,0.588235,200.900004,136.6,0.05001033271337053 +comparator,sky130,16,1500,0.6666667,358.680007,189.253,0.06321553011448482 +comparator,sky130,32,1300,0.7692307,690.900013,315.709,0.10771793448084398 +comparator,sky130,64,1200,0.8333333,1372.980026,508.393,0.2048577820389901 +comparator,sky130,128,1150,0.869565,2744.980052,796.047,0.34396273737011823 flop,sky130,8,1000,1.0000,133.279999,64.8145,0.193835 flop,sky130,16,1000,1.0000,266.5599975,129.629,0.38715000000000005 flop,sky130,32,1000,1.0000,533.119995,259.258,0.7723000000000001 diff --git a/synthDC/ppa/ppaAnalyze.py b/synthDC/ppa/ppaAnalyze.py index 73cd353c4..bd98e79be 100755 --- a/synthDC/ppa/ppaAnalyze.py +++ b/synthDC/ppa/ppaAnalyze.py @@ -18,92 +18,115 @@ from collections import namedtuple import sklearn.metrics as skm # depricated, will need to replace with scikit-learn import os + def synthsfromcsv(filename): Synth = namedtuple("Synth", "module tech width freq delay area lpower denergy") - with open(filename, newline='') as csvfile: + with open(filename, newline="") as csvfile: csvreader = csv.reader(csvfile) global allSynths allSynths = list(csvreader)[1:] for i in range(len(allSynths)): for j in range(len(allSynths[0])): - try: allSynths[i][j] = int(allSynths[i][j]) - except: - try: allSynths[i][j] = float(allSynths[i][j]) - except: pass + try: + allSynths[i][j] = int(allSynths[i][j]) + except: + try: + allSynths[i][j] = float(allSynths[i][j]) + except: + pass allSynths[i] = Synth(*allSynths[i]) return allSynths - + + def synthsintocsv(): - ''' writes a CSV with one line for every available synthesis - each line contains the module, tech, width, target freq, and resulting metrics - ''' + """writes a CSV with one line for every available synthesis + each line contains the module, tech, width, target freq, and resulting metrics + """ print("This takes a moment...") bashCommand = "find . -path '*runs/*' -prune" - output = subprocess.check_output(['bash','-c', bashCommand]) - allSynths = output.decode("utf-8").split('\n')[:-1] + output = subprocess.check_output(["bash", "-c", bashCommand]) + allSynths = output.decode("utf-8").split("\n")[:-1] - specReg = re.compile('[a-zA-Z0-9]+') - metricReg = re.compile('-?\d+\.\d+[e]?[-+]?\d*') + specReg = re.compile("[a-zA-Z0-9]+") + metricReg = re.compile("-?\d+\.\d+[e]?[-+]?\d*") file = open("ppaData.csv", "w") writer = csv.writer(file) - writer.writerow(['Module', 'Tech', 'Width', 'Target Freq', 'Delay', 'Area', 'L Power (nW)', 'D energy (nJ)']) + writer.writerow( + [ + "Module", + "Tech", + "Width", + "Target Freq", + "Delay", + "Area", + "L Power (nW)", + "D energy (nJ)", + ] + ) for oneSynth in allSynths: module, width, risc, tech, freq = specReg.findall(oneSynth)[1:6] metrics = [] - for phrase in [['Path Slack', 'qor'], ['Design Area', 'qor'], ['100', 'power']]: - bashCommand = 'grep "{}" '+ oneSynth[2:]+'/reports/*{}*' + for phrase in [["Path Slack", "qor"], ["Design Area", "qor"], ["100", "power"]]: + bashCommand = 'grep "{}" ' + oneSynth[2:] + "/reports/*{}*" bashCommand = bashCommand.format(*phrase) - try: output = subprocess.check_output(['bash','-c', bashCommand]) - except: + try: + output = subprocess.check_output(["bash", "-c", bashCommand]) + except: print(module + width + tech + freq + " doesn't have reports") print("Consider running cleanup() first") nums = metricReg.findall(str(output)) nums = [float(m) for m in nums] metrics += nums - delay = 1000/int(freq) - metrics[0] + delay = 1000 / int(freq) - metrics[0] area = metrics[1] lpower = metrics[4] - denergy = (metrics[2] + metrics[3])/int(freq)*1000 # (switching + internal powers)*delay, more practical units for regression coefs + tpower = (metrics[2] + metrics[3] + metrics[4]*.000001) + denergy = ( + (tpower) / int(freq) * 1000 + ) # (switching + internal powers)*delay, more practical units for regression coefs - if ('flop' in module): # since two flops in each module - [area, lpower, denergy] = [n/2 for n in [area, lpower, denergy]] + if "flop" in module: # since two flops in each module + [area, lpower, denergy] = [n / 2 for n in [area, lpower, denergy]] writer.writerow([module, tech, width, freq, delay, area, lpower, denergy]) file.close() + def cleanup(): - ''' removes runs that didn't work - ''' + """removes runs that didn't work""" bashCommand = 'grep -r "Error" runs/ppa*/reports/*qor*' - try: - output = subprocess.check_output(['bash','-c', bashCommand]) - allSynths = output.decode("utf-8").split('\n')[:-1] + try: + output = subprocess.check_output(["bash", "-c", bashCommand]) + allSynths = output.decode("utf-8").split("\n")[:-1] for run in allSynths: - run = run.split('MHz')[0] - bc = 'rm -r '+ run + '*' - output = subprocess.check_output(['bash','-c', bc]) - except: pass + run = run.split("MHz")[0] + bc = "rm -r " + run + "*" + output = subprocess.check_output(["bash", "-c", bc]) + except: + pass bashCommand = "find . -path '*runs/*' -prune" - output = subprocess.check_output(['bash','-c', bashCommand]) - allSynths = output.decode("utf-8").split('\n')[:-1] + output = subprocess.check_output(["bash", "-c", bashCommand]) + allSynths = output.decode("utf-8").split("\n")[:-1] for oneSynth in allSynths: - for phrase in [['Path Length', 'qor']]: - bashCommand = 'grep "{}" '+ oneSynth[2:]+'/reports/*{}*' + for phrase in [["Path Length", "qor"]]: + bashCommand = 'grep "{}" ' + oneSynth[2:] + "/reports/*{}*" bashCommand = bashCommand.format(*phrase) - try: output = subprocess.check_output(['bash','-c', bashCommand]) - except: - bc = 'rm -r '+ oneSynth[2:] - output = subprocess.check_output(['bash','-c', bc]) + try: + output = subprocess.check_output(["bash", "-c", bashCommand]) + except: + bc = "rm -r " + oneSynth[2:] + output = subprocess.check_output(["bash", "-c", bc]) print("All cleaned up!") + def getVals(tech, module, var, freq=None, width=None): - ''' for a specified tech, module, and variable/metric - returns a list of values for that metric in ascending width order - works at a specified target frequency or if none is given, uses the synthesis with the best achievable delay for each width - ''' + """for a specified tech, module, and variable/metric + returns a list of values for that metric in ascending width order + works at a specified target frequency or if none is given, uses the synthesis with the best achievable delay for each width + """ if width != None: widthsToGet = width @@ -113,85 +136,132 @@ def getVals(tech, module, var, freq=None, width=None): metric = [] widthL = [] - if (freq != None): + if freq != None: for oneSynth in allSynths: - if (oneSynth.freq == freq) & (oneSynth.tech == tech) & (oneSynth.module == module) & (oneSynth.width != 1): + if ( + (oneSynth.freq == freq) + & (oneSynth.tech == tech) + & (oneSynth.module == module) + & (oneSynth.width != 1) + ): widthL += [oneSynth.width] osdict = oneSynth._asdict() metric += [osdict[var]] - metric = [x for _, x in sorted(zip(widthL, metric))] # ordering + metric = [x for _, x in sorted(zip(widthL, metric))] # ordering else: for w in widthsToGet: for oneSynth in bestSynths: - if (oneSynth.width == w) & (oneSynth.tech == tech) & (oneSynth.module == module): + if ( + (oneSynth.width == w) + & (oneSynth.tech == tech) + & (oneSynth.module == module) + ): osdict = oneSynth._asdict() met = osdict[var] metric += [met] return metric + def csvOfBest(filename): bestSynths = [] for tech in [x.tech for x in techSpecs]: for mod in modules: for w in widths: - m = np.Inf # large number to start + m = np.Inf # large number to start best = None - for oneSynth in allSynths: # best achievable, rightmost green - if (oneSynth.width == w) & (oneSynth.tech == tech) & (oneSynth.module == mod): - if (oneSynth.delay < m) & (1000/oneSynth.delay > oneSynth.freq): + for oneSynth in allSynths: # best achievable, rightmost green + if ( + (oneSynth.width == w) + & (oneSynth.tech == tech) + & (oneSynth.module == mod) + ): + if (oneSynth.delay < m) & ( + 1000 / oneSynth.delay > oneSynth.freq + ): m = oneSynth.delay best = oneSynth if (best != None) & (best not in bestSynths): bestSynths += [best] - + file = open(filename, "w") writer = csv.writer(file) - writer.writerow(['Module', 'Tech', 'Width', 'Target Freq', 'Delay', 'Area', 'L Power (nW)', 'D energy (nJ)']) + writer.writerow( + [ + "Module", + "Tech", + "Width", + "Target Freq", + "Delay", + "Area", + "L Power (nW)", + "D energy (nJ)", + ] + ) for synth in bestSynths: writer.writerow(list(synth)) file.close() return bestSynths - + + def genLegend(fits, coefs, r2=None, spec=None, ale=False): - ''' generates a list of two legend elements (or just an equation if no r2 or spec) - labels line with fit equation and dots with r squared of the fit - ''' + """generates a list of two legend elements (or just an equation if no r2 or spec) + labels line with fit equation and dots with r squared of the fit + """ coefsr = [str(sigfig(c, 2)) for c in coefs] if ale: - if (normAddWidth == 32): - sub = 'S' + if normAddWidth == 32: + sub = "S" elif normAddWidth != 1: - print('Equations are wrong, check normAddWidth') + print("Equations are wrong, check normAddWidth") else: - sub = 'N' + sub = "N" - eqDict = {'c': '', 'l': sub, 's': '$'+sub+'^2$', 'g': '$log_2$('+sub+')', 'n': ''+sub+'$log_2$('+sub+')'} - eq = '' - ind = 0 + eqDict = { + "c": "", + "l": sub, + "s": "$" + sub + "^2$", + "g": "$log_2$(" + sub + ")", + "n": "" + sub + "$log_2$(" + sub + ")", + } + eq = "" + ind = 0 for k in eqDict.keys(): if k in fits: - if str(coefsr[ind]) != '0': eq += " + " + coefsr[ind] + eqDict[k] + if str(coefsr[ind]) != "0": + eq += " + " + coefsr[ind] + eqDict[k] ind += 1 - eq = eq[3:] # chop off leading ' + ' + eq = eq[3:] # chop off leading ' + ' - if (r2==None) or (spec==None): + if (r2 == None) or (spec == None): return eq else: legend_elements = [lines.Line2D([0], [0], color=spec.color, label=eq)] - legend_elements += [lines.Line2D([0], [0], color=spec.color, ls='', marker=spec.shape, label='$R^2$='+ str(round(r2, 4)))] + legend_elements += [ + lines.Line2D( + [0], + [0], + color=spec.color, + ls="", + marker=spec.shape, + label="$R^2$=" + str(round(r2, 4)), + ) + ] return legend_elements -def oneMetricPlot(module, widths, var, freq=None, ax=None, fits='clsgn', norm=True, color=None): - ''' module: string module name - freq: int freq (MHz) - var: string delay, area, lpower, or denergy - fits: constant, linear, square, log2, Nlog2 - plots given variable vs width for all matching syntheses with regression - ''' + +def oneMetricPlot( + module, widths, var, freq=None, ax=None, fits="clsgn", norm=True, color=None +): + """module: string module name + freq: int freq (MHz) + var: string delay, area, lpower, or denergy + fits: constant, linear, square, log2, Nlog2 + plots given variable vs width for all matching syntheses with regression + """ singlePlot = True if ax or (freq == 10): singlePlot = False @@ -202,24 +272,27 @@ def oneMetricPlot(module, widths, var, freq=None, ax=None, fits='clsgn', norm=Tr allWidths = [] allMetrics = [] - ale = (var != 'delay') # if not delay, must be area, leakage, or energy + ale = var != "delay" # if not delay, must be area, leakage, or energy modFit = fitDict[module] fits = modFit[ale] if freq: - ls = '--' + ls = "--" else: - ls = '-' + ls = "-" for spec in techSpecs: + # print(f"Searching for module of spec {spec} and module {module} and var {var}") metric = getVals(spec.tech, module, var, freq=freq) - + # print(f"Found metric : {metric}") if norm: techdict = spec._asdict() norm = techdict[var] - metric = [m/norm for m in metric] + metric = [m / norm for m in metric] - if len(metric) == 5: # don't include the spec if we don't have points for all widths + if len(widths) == len(metric): + # don't include the spec if we don't have points for all widths + # print(f"Width \neq Metric") xp, pred, coefs, r2 = regress(widths, metric, fits, ale) fullLeg += genLegend(fits, coefs, r2, spec, ale=ale) c = color if color else spec.color @@ -228,44 +301,77 @@ def oneMetricPlot(module, widths, var, freq=None, ax=None, fits='clsgn', norm=Tr allWidths += widths allMetrics += metric - xp, pred, coefs, r2 = regress(allWidths, allMetrics, fits) - ax.plot(xp, pred, color='red', linestyle=ls) + # print(f"Widths passed into regress : {allWidths}") + if len(allWidths) > 0: + xp, pred, coefs, r2 = regress(allWidths, allMetrics, fits) + ax.plot(xp, pred, color="orange", linestyle=ls) + else: + xp, pred, coefs, r2 = regress(widths, metric, fits) + ax.plot(xp, pred, color="orange", linestyle=ls) if norm: - ylabeldic = {"lpower": "Leakage Power (add32)", "denergy": "Energy/Op (add32)", "area": "Area (add32)", "delay": "Delay (FO4)"} + ylabeldic = { + "lpower": "Leakage Power (add32)", + "denergy": "Energy/Op (add32)", + "area": "Area (add32)", + "delay": "Delay (FO4)", + } else: - ylabeldic = {"lpower": "Leakage Power (nW)", "denergy": "Dynamic Energy (nJ)", "area": "Area (sq microns)", "delay": "Delay (ns)"} + ylabeldic = { + "lpower": "Leakage Power (nW)", + "denergy": "Dynamic Energy (nJ)", + "area": "Area (sq microns)", + "delay": "Delay (ns)", + } ax.set_ylabel(ylabeldic[var]) ax.set_xticks(widths) - if singlePlot or (var == 'lpower') or (var == 'denergy'): + if singlePlot or (var == "lpower") or (var == "denergy"): ax.set_xlabel("Width (bits)") - if not singlePlot and ((var == 'delay') or (var == 'area')): - ax.tick_params(labelbottom=False) + if not singlePlot and ((var == "delay") or (var == "area")): + ax.tick_params(labelbottom=False) if singlePlot: fullLeg += genLegend(fits, coefs, r2, combined, ale=ale) - legLoc = 'upper left' if ale else 'center right' + legLoc = "upper left" if ale else "center right" ax.add_artist(ax.legend(handles=fullLeg, loc=legLoc)) - titleStr = " (target " + str(freq)+ "MHz)" if freq != None else " (best achievable delay)" + titleStr = ( + " (target " + str(freq) + "MHz)" + if freq != None + else " (best achievable delay)" + ) ax.set_title(module + titleStr) - plt.savefig('.plots/'+ module + '_' + var + '.png') + plt.savefig(".plots/" + module + "_" + var + ".png") # plt.show() return r2 -def regress(widths, var, fits='clsgn', ale=False): - ''' fits a curve to the given points - returns lists of x and y values to plot that curve and coefs for the eq with r2 - ''' +def regress(widths, var, fits="clsgn", ale=False): + """fits a curve to the given points + returns lists of x and y values to plot that curve and coefs for the eq with r2 + """ + if len(var) != len(widths): + # print( + # f"There are not enough variables to match widths. Widths : {widths} Variables Found : {var}, padding to match may affect correctness (doing it anyways)\n" + # ) + if len(widths) > len(var): + while len(widths) > len(var): + var.append(0.0) + if len(var) > len(widths): + while len(var) > len(widths): + widths.append(0) + + # widths = [8, 16, 32, 64, 128] + # print(f"Regress var : {var}") + # print(f"Regress widths : {widths}") funcArr = genFuncs(fits) - xp = np.linspace(min(widths)/2, max(widths)*1.1, 200) + xp = np.linspace(min(widths) / 2, max(widths) * 1.1, 200) xpToCalc = xp if ale: - widths = [w/normAddWidth for w in widths] - xpToCalc = [x/normAddWidth for x in xp] + widths = [w / normAddWidth for w in widths] + xpToCalc = [x / normAddWidth for x in xp] mat = [] for w in widths: @@ -273,8 +379,9 @@ def regress(widths, var, fits='clsgn', ale=False): for func in funcArr: row += [func(w)] mat += [row] - - y = np.array(var, dtype=np.float) + + # var = [0, 1, 2, 3, 4] + y = np.array(var, dtype=np.float64) coefs = opt.nnls(mat, y)[0] yp = [] @@ -290,19 +397,22 @@ def regress(widths, var, fits='clsgn', ale=False): return xp, pred, coefs, r2 + def makeCoefTable(): - ''' writes CSV with each line containing the coefficients for a regression fit - to a particular combination of module, metric (including both techs, normalized) - ''' + """writes CSV with each line containing the coefficients for a regression fit + to a particular combination of module, metric (including both techs, normalized) + """ file = open("ppaFitting.csv", "w") writer = csv.writer(file) - writer.writerow(['Module', 'Metric', 'Target', '1', 'N', 'N^2', 'log2(N)', 'Nlog2(N)', 'R^2']) + writer.writerow( + ["Module", "Metric", "Target", "1", "N", "N^2", "log2(N)", "Nlog2(N)", "R^2"] + ) for module in modules: for freq in [10, None]: - target = 'easy' if freq else 'hard' - for var in ['delay', 'area', 'lpower', 'denergy']: - ale = (var != 'delay') + target = "easy" if freq else "hard" + for var in ["delay", "area", "lpower", "denergy"]: + ale = var != "delay" metL = [] modFit = fitDict[module] fits = modFit[ale] @@ -311,12 +421,12 @@ def makeCoefTable(): metric = getVals(spec.tech, module, var, freq=freq) techdict = spec._asdict() norm = techdict[var] - metL += [m/norm for m in metric] + metL += [m / norm for m in metric] - xp, pred, coefs, r2 = regress(widths*2, metL, fits, ale) + xp, pred, coefs, r2 = regress(widths * 2, metL, fits, ale) coefs = np.ndarray.tolist(coefs) - coefsToWrite = [None]*5 - fitTerms = 'clsgn' + coefsToWrite = [None] * 5 + fitTerms = "clsgn" ind = 0 for i in range(len(fitTerms)): if fitTerms[i] in fits: @@ -327,25 +437,38 @@ def makeCoefTable(): file.close() + def sigfig(num, figs): - return '{:g}'.format(float('{:.{p}g}'.format(num, p=figs))) + return "{:g}".format(float("{:.{p}g}".format(num, p=figs))) + def makeEqTable(): - ''' writes CSV with each line containing the equations for fits for each metric - to a particular module (including both techs, normalized) - ''' + """writes CSV with each line containing the equations for fits for each metric + to a particular module (including both techs, normalized) + """ file = open("ppaEquations.csv", "w") writer = csv.writer(file) - writer.writerow(['Element', 'Best delay', 'Fast area', 'Fast leakage', 'Fast energy', 'Small area', 'Small leakage', 'Small energy']) + writer.writerow( + [ + "Element", + "Best delay", + "Fast area", + "Fast leakage", + "Fast energy", + "Small area", + "Small leakage", + "Small energy", + ] + ) for module in modules: eqs = [] for freq in [None, 10]: - for var in ['delay', 'area', 'lpower', 'denergy']: - if (var == 'delay') and (freq == 10): + for var in ["delay", "area", "lpower", "denergy"]: + if (var == "delay") and (freq == 10): pass else: - ale = (var != 'delay') + ale = var != "delay" metL = [] modFit = fitDict[module] fits = modFit[ale] @@ -354,9 +477,9 @@ def makeEqTable(): metric = getVals(spec.tech, module, var, freq=freq) techdict = spec._asdict() norm = techdict[var] - metL += [m/norm for m in metric] + metL += [m / norm for m in metric] - xp, pred, coefs, r2 = regress(widths*2, metL, fits, ale) + xp, pred, coefs, r2 = regress(widths * 2, metL, fits, ale) coefs = np.ndarray.tolist(coefs) eqs += [genLegend(fits, coefs, ale=ale)] row = [module] + eqs @@ -364,93 +487,113 @@ def makeEqTable(): file.close() -def genFuncs(fits='clsgn'): - ''' helper function for regress() - returns array of functions with one for each term desired in the regression fit - ''' + +def genFuncs(fits="clsgn"): + """helper function for regress() + returns array of functions with one for each term desired in the regression fit + """ funcArr = [] - if 'c' in fits: + if "c" in fits: funcArr += [lambda x: 1] - if 'l' in fits: + if "l" in fits: funcArr += [lambda x: x] - if 's' in fits: + if "s" in fits: funcArr += [lambda x: x**2] - if 'g' in fits: + if "g" in fits: funcArr += [lambda x: np.log2(x)] - if 'n' in fits: - funcArr += [lambda x: x*np.log2(x)] + if "n" in fits: + funcArr += [lambda x: x * np.log2(x)] return funcArr + def noOutliers(median, freqs, delays, areas): - ''' returns a pared down list of freqs, delays, and areas - cuts out any syntheses in which target freq isn't within 75% of the min delay target to focus on interesting area - helper function to freqPlot() - ''' - f=[] - d=[] - a=[] + """returns a pared down list of freqs, delays, and areas + cuts out any syntheses in which target freq isn't within 75% of the min delay target to focus on interesting area + helper function to freqPlot() + """ + f = [] + d = [] + a = [] for i in range(len(freqs)): - norm = freqs[i]/median - if (norm > 0.4) & (norm<1.4): + norm = freqs[i] / median + if (norm > 0.4) & (norm < 1.4): f += [freqs[i]] d += [delays[i]] a += [areas[i]] - + return f, d, a + def freqPlot(tech, mod, width): - ''' plots delay, area, area*delay, and area*delay^2 for syntheses with specified tech, module, width - ''' + """plots delay, area, area*delay, and area*delay^2 for syntheses with specified tech, module, width""" freqsL, delaysL, areasL = ([[], []] for i in range(3)) for oneSynth in allSynths: - if (mod == oneSynth.module) & (width == oneSynth.width) & (tech == oneSynth.tech): - ind = (1000/oneSynth.delay < oneSynth.freq) # when delay is within target clock period + if ( + (mod == oneSynth.module) + & (width == oneSynth.width) + & (tech == oneSynth.tech) + ): + ind = ( + 1000 / oneSynth.delay < oneSynth.freq + ) # when delay is within target clock period freqsL[ind] += [oneSynth.freq] delaysL[ind] += [oneSynth.delay] areasL[ind] += [oneSynth.area] median = np.median(list(flatten(freqsL))) - + f, (ax1, ax2) = plt.subplots(2, 1, sharex=True) for ax in (ax1, ax2): - ax.ticklabel_format(useOffset=False, style='plain') + ax.ticklabel_format(useOffset=False, style="plain") - for ind in [0,1]: + for ind in [0, 1]: areas = areasL[ind] delays = delaysL[ind] freqs = freqsL[ind] - freqs, delays, areas = noOutliers(median, freqs, delays, areas) # comment out to see all syntheses + freqs, delays, areas = noOutliers( + median, freqs, delays, areas + ) # comment out to see all syntheses - c = 'blue' if ind else 'green' + c = "blue" if ind else "green" ax1.scatter(freqs, delays, color=c) ax2.scatter(freqs, areas, color=c) - legend_elements = [lines.Line2D([0], [0], color='green', ls='', marker='o', label='timing achieved'), - lines.Line2D([0], [0], color='blue', ls='', marker='o', label='slack violated')] + legend_elements = [ + lines.Line2D( + [0], [0], color="green", ls="", marker="o", label="timing achieved" + ), + lines.Line2D([0], [0], color="blue", ls="", marker="o", label="slack violated"), + ] ax1.legend(handles=legend_elements) width = str(width) - + ax2.set_xlabel("Target Freq (MHz)") - ax1.set_ylabel('Delay (ns)') - ax2.set_ylabel('Area (sq microns)') - ax1.set_title(mod + '_' + width) - if ('mux' in mod) & ('d' in mod): + ax1.set_ylabel("Delay (ns)") + ax2.set_ylabel("Area (sq microns)") + ax1.set_title(mod + "_" + width) + if ("mux" in mod) & ("d" in mod): width = mod - mod = 'muxd' - plt.savefig('./plots/freqBuckshot/' + tech + '/' + mod + '/' + width + '.png') + mod = "muxd" + plt.savefig("./plots/freqBuckshot/" + tech + "/" + mod + "/" + width + ".png") # plt.show() + def squareAreaDelay(tech, mod, width): - ''' plots delay, area, area*delay, and area*delay^2 for syntheses with specified tech, module, width - ''' + """plots delay, area, area*delay, and area*delay^2 for syntheses with specified tech, module, width""" global allSynths freqsL, delaysL, areasL = ([[], []] for i in range(3)) for oneSynth in allSynths: - if (mod == oneSynth.module) & (width == oneSynth.width) & (tech == oneSynth.tech): - ind = (1000/oneSynth.delay < oneSynth.freq) # when delay is within target clock period + if ( + (mod == oneSynth.module) + & (width == oneSynth.width) + & (tech == oneSynth.tech) + ): + ind = ( + 1000 / oneSynth.delay < oneSynth.freq + ) # when delay is within target clock period freqsL[ind] += [oneSynth.freq] delaysL[ind] += [oneSynth.delay] areasL[ind] += [oneSynth.area] @@ -458,182 +601,212 @@ def squareAreaDelay(tech, mod, width): f, (ax1) = plt.subplots(1, 1) ax2 = ax1.twinx() - for ind in [0,1]: + for ind in [0, 1]: areas = areasL[ind] delays = delaysL[ind] targets = freqsL[ind] - targets = [1000/f for f in targets] - - targets, delays, areas = noOutliers(targets, delays, areas) # comment out to see all - + targets = [1000 / f for f in targets] + + targets, delays, areas = noOutliers( + targets, delays, areas + ) # comment out to see all + if not ind: achievedDelays = delays - c = 'blue' if ind else 'green' - ax1.scatter(targets, delays, marker='^', color=c) - ax2.scatter(targets, areas, marker='s', color=c) - - bestAchieved = min(achievedDelays) - - legend_elements = [lines.Line2D([0], [0], color='green', ls='', marker='^', label='delay (timing achieved)'), - lines.Line2D([0], [0], color='green', ls='', marker='s', label='area (timing achieved)'), - lines.Line2D([0], [0], color='blue', ls='', marker='^', label='delay (timing violated)'), - lines.Line2D([0], [0], color='blue', ls='', marker='s', label='area (timing violated)')] + c = "blue" if ind else "green" + ax1.scatter(targets, delays, marker="^", color=c) + ax2.scatter(targets, areas, marker="s", color=c) + + bestAchieved = min(achievedDelays) + + legend_elements = [ + lines.Line2D( + [0], [0], color="green", ls="", marker="^", label="delay (timing achieved)" + ), + lines.Line2D( + [0], [0], color="green", ls="", marker="s", label="area (timing achieved)" + ), + lines.Line2D( + [0], [0], color="blue", ls="", marker="^", label="delay (timing violated)" + ), + lines.Line2D( + [0], [0], color="blue", ls="", marker="s", label="area (timing violated)" + ), + ] + + ax2.legend(handles=legend_elements, loc="upper left") - ax2.legend(handles=legend_elements, loc='upper left') - ax1.set_xlabel("Delay Targeted (ns)") ax1.set_ylabel("Delay Achieved (ns)") - ax2.set_ylabel('Area (sq microns)') - ax1.set_title(mod + '_' + str(width)) + ax2.set_ylabel("Area (sq microns)") + ax1.set_title(mod + "_" + str(width)) squarify(f) xvals = np.array(ax1.get_xlim()) - frac = (min(flatten(delaysL))-xvals[0])/(xvals[1]-xvals[0]) - areaLowerLim = min(flatten(areasL))-100 - areaUpperLim = max(flatten(areasL))/frac + areaLowerLim + frac = (min(flatten(delaysL)) - xvals[0]) / (xvals[1] - xvals[0]) + areaLowerLim = min(flatten(areasL)) - 100 + areaUpperLim = max(flatten(areasL)) / frac + areaLowerLim ax2.set_ylim([areaLowerLim, areaUpperLim]) ax1.plot(xvals, xvals, ls="--", c=".3") - ax1.hlines(y=bestAchieved, xmin=xvals[0], xmax=xvals[1], color="black", ls='--') + ax1.hlines(y=bestAchieved, xmin=xvals[0], xmax=xvals[1], color="black", ls="--") - plt.savefig('./plots/squareareadelay_' + mod + '_' + str(width) + '.png') + plt.savefig("./plots/squareareadelay_" + mod + "_" + str(width) + ".png") # plt.show() + def squarify(fig): - ''' helper function for squareAreaDelay() - forces matplotlib figure to be a square - ''' + """helper function for squareAreaDelay() + forces matplotlib figure to be a square + """ w, h = fig.get_size_inches() if w > h: t = fig.subplotpars.top b = fig.subplotpars.bottom - axs = h*(t-b) - l = (1.-axs/w)/2 - fig.subplots_adjust(left=l, right=1-l) + axs = h * (t - b) + l = (1.0 - axs / w) / 2 + fig.subplots_adjust(left=l, right=1 - l) else: t = fig.subplotpars.right b = fig.subplotpars.left - axs = w*(t-b) - l = (1.-axs/h)/2 - fig.subplots_adjust(bottom=l, top=1-l) + axs = w * (t - b) + l = (1.0 - axs / h) / 2 + fig.subplots_adjust(bottom=l, top=1 - l) -def plotPPA(mod, widths, freq=None, norm=True, aleOpt=False): - ''' for the module specified, plots width vs delay, area, leakage power, and dynamic energy with fits - if no freq specified, uses the synthesis with best achievable delay for each width - overlays data from both techs - ''' - with mpl.rc_context({"figure.figsize": (7,3.46)}): + +def plotPPA(mod, freq=None, norm=True, aleOpt=False): + """for the module specified, plots width vs delay, area, leakage power, and dynamic energy with fits + if no freq specified, uses the synthesis with best achievable delay for each width + overlays data from both techs + """ + with mpl.rc_context({"figure.figsize": (7, 3.46)}): fig, axs = plt.subplots(2, 2) - arr = [['delay', 'area'], ['lpower', 'denergy']] + arr = [["delay", "area"], ["lpower", "denergy"]] freqs = [freq] - if aleOpt: freqs += [10] + if aleOpt: + freqs += [10] for i in [0, 1]: for j in [0, 1]: leg = [] for f in freqs: - if (arr[i][j]=='delay') and (f==10): + if (arr[i][j] == "delay") and (f == 10): pass else: - r2 = oneMetricPlot(mod, widths, arr[i][j], ax=axs[i, j], freq=f, norm=norm) - ls = '--' if f else '-' - leg += [lines.Line2D([0], [0], color='red', label='$R^2$='+str(round(r2, 4)), linestyle=ls)] + # print(f"Pasing in widths {widths}") + r2 = oneMetricPlot( + mod, widths, arr[i][j], ax=axs[i, j], freq=f, norm=norm + ) + ls = "--" if f else "-" + leg += [ + lines.Line2D( + [0], + [0], + color="orange", + label="$R^2$=" + str(round(r2, 4)), + linestyle=ls, + ) + ] - if (mod in ['flop', 'csa']) & (arr[i][j] == 'delay'): + if (mod in ["flop", "csa"]) & (arr[i][j] == "delay"): axs[i, j].set_ylim(ymin=0) ytop = axs[i, j].get_ylim()[1] - axs[i, j].set_ylim(ymax=1.1*ytop) + axs[i, j].set_ylim(ymax=1.1 * ytop) else: axs[i, j].legend(handles=leg, handlelength=1.5) - - titleStr = " (target " + str(freq)+ "MHz)" if freq != None else "" - plt.suptitle(mod + titleStr) - plt.tight_layout(pad=0.05, w_pad=1, h_pad=0.5, rect=(0,0,1,0.97)) - if freq != 10: - n = 'normalized' if norm else 'unnormalized' - saveStr = './plots/'+ n + '/' + mod + '.png' + titleStr = " (target " + str(freq) + "MHz)" if freq != None else "" + plt.suptitle(mod + titleStr) + plt.tight_layout(pad=0.05, w_pad=1, h_pad=0.5, rect=(0, 0, 1, 0.97)) + + if freq != 10: + n = "normalized" if norm else "unnormalized" + saveStr = "./plots/" + n + "/" + mod + "_" + ".png" + print(f"Saving to {saveStr}") plt.savefig(saveStr) # plt.show() + def makeLineLegend(): - ''' generates legend to accompany normalized plots - ''' - plt.rcParams["figure.figsize"] = (5.5,0.3) + """generates legend to accompany normalized plots""" + plt.rcParams["figure.figsize"] = (5.5, 0.3) fig = plt.figure() - fullLeg = [lines.Line2D([0], [0], color='black', label='fastest', linestyle='-')] - fullLeg += [lines.Line2D([0], [0], color='black', label='smallest', linestyle='--')] - fullLeg += [lines.Line2D([0], [0], color='blue', label='tsmc28', marker='^')] - fullLeg += [lines.Line2D([0], [0], color='blue', label='tsmc28psyn', marker='x')] - fullLeg += [lines.Line2D([0], [0], color='green', label='sky90', marker='o')] - fullLeg += [lines.Line2D([0], [0], color='green', label='sky130', marker='+')] - fullLeg += [lines.Line2D([0], [0], color='red', label='combined', marker='_')] - fig.legend(handles=fullLeg, ncol=5, handlelength=1.4, loc='center') - saveStr = './plots/legend.png' + fullLeg = [lines.Line2D([0], [0], color="black", label="fastest", linestyle="-")] + fullLeg += [lines.Line2D([0], [0], color="black", label="smallest", linestyle="--")] + fullLeg += [lines.Line2D([0], [0], color="blue", label="tsmc28", marker="^")] + fullLeg += [lines.Line2D([0], [0], color="blue", label="tsmc28psyn", marker="x")] + fullLeg += [lines.Line2D([0], [0], color="green", label="sky90", marker="o")] + fullLeg += [lines.Line2D([0], [0], color="purple", label="sky130", marker="+")] + fullLeg += [lines.Line2D([0], [0], color="orange", label="combined", marker="_")] + fig.legend(handles=fullLeg, ncol=5, handlelength=1.4, loc="center") + saveStr = "./plots/legend.png" plt.savefig(saveStr) -def muxPlot(fits='clsgn', norm=True): - ''' module: string module name - freq: int freq (MHz) - var: string delay, area, lpower, or denergy - fits: constant, linear, square, log2, Nlog2 - plots given variable vs width for all matching syntheses with regression - ''' + +def muxPlot(fits="clsgn", norm=True): + """module: string module name + freq: int freq (MHz) + var: string delay, area, lpower, or denergy + fits: constant, linear, square, log2, Nlog2 + plots given variable vs width for all matching syntheses with regression + """ ax = plt.gca() inputs = [2, 4, 8] - allInputs = inputs*2 + allInputs = inputs * 2 fullLeg = [] - for crit in ['data', 'control']: + for crit in ["data", "control"]: allMetrics = [] - muxes = ['mux2', 'mux4', 'mux8'] + muxes = ["mux2", "mux4", "mux8"] - if crit == 'data': - ls = '--' - muxes = [m + 'd' for m in muxes] - elif crit == 'control': - ls = '-' + if crit == "data": + ls = "--" + muxes = [m + "d" for m in muxes] + elif crit == "control": + ls = "-" for spec in techSpecs: metric = [] for module in muxes: - metric += getVals(spec.tech, module, 'delay', width=[1]) - + metric += getVals(spec.tech, module, "delay", width=[1]) + if norm: techdict = spec._asdict() - norm = techdict['delay'] - metric = [m/norm for m in metric] + norm = techdict["delay"] + metric = [m / norm for m in metric] # print(spec.tech, ' ', metric) - if len(metric) == 3: # don't include the spec if we don't have points for all + if ( + len(metric) == 3 + ): # don't include the spec if we don't have points for all xp, pred, coefs, r2 = regress(inputs, metric, fits, ale=False) ax.scatter(inputs, metric, color=spec.color, marker=spec.shape) ax.plot(xp, pred, color=spec.color, linestyle=ls) allMetrics += metric xp, pred, coefs, r2 = regress(allInputs, allMetrics, fits) - ax.plot(xp, pred, color='red', linestyle=ls) - fullLeg += [lines.Line2D([0], [0], color='red', label=crit, linestyle=ls)] - - ax.set_ylabel('Delay (FO4)') + ax.plot(xp, pred, color="red", linestyle=ls) + fullLeg += [lines.Line2D([0], [0], color="red", label=crit, linestyle=ls)] + + ax.set_ylabel("Delay (FO4)") ax.set_xticks(inputs) ax.set_xlabel("Number of inputs") - ax.set_title('mux timing') - - ax.legend(handles = fullLeg) - plt.savefig('./plots/mux.png') + ax.set_title("mux timing") + + ax.legend(handles=fullLeg) + plt.savefig("./plots/mux.png") + def stdDevError(): - ''' calculates std deviation and error for paper-writing purposes - ''' - for var in ['delay', 'area', 'lpower', 'denergy']: + """calculates std deviation and error for paper-writing purposes""" + for var in ["delay", "area", "lpower", "denergy"]: errlist = [] for module in modules: - ale = (var != 'delay') + ale = var != "delay" metL = [] modFit = fitDict[module] fits = modFit[ale] @@ -643,20 +816,20 @@ def stdDevError(): metric = getVals(spec.tech, module, var) techdict = spec._asdict() norm = techdict[var] - metL += [m/norm for m in metric] + metL += [m / norm for m in metric] if ale: - ws = [w/normAddWidth for w in widths] + ws = [w / normAddWidth for w in widths] else: ws = widths - ws = ws*2 + ws = ws * 2 mat = [] for w in ws: row = [] for func in funcArr: row += [func(w)] mat += [row] - + y = np.array(metL, dtype=np.float) coefs = opt.nnls(mat, y)[0] @@ -665,68 +838,84 @@ def stdDevError(): n = [func(w) for func in funcArr] yp += [sum(np.multiply(coefs, n))] - if (var == 'delay') & (module == 'flop'): + if (var == "delay") & (module == "flop"): pass - elif (module == 'mult') & ale: + elif (module == "mult") & ale: pass else: for i in range(len(y)): - errlist += [abs(y[i]/yp[i]-1)] + errlist += [abs(y[i] / yp[i] - 1)] # print(module, ' ', var, ' ', np.mean(errlist[-10:])) - + avgErr = np.mean(errlist) stdv = np.std(errlist) - print(var, ' ', avgErr, ' ', stdv) + print(var, " ", avgErr, " ", stdv) + def makePlotDirectory(): - ''' creates plots directory in same level as this script to store plots in - ''' + """creates plots directory in same level as this script to store plots in""" current_directory = os.getcwd() - final_directory = os.path.join(current_directory, 'plots') + final_directory = os.path.join(current_directory, "plots") if not os.path.exists(final_directory): os.makedirs(final_directory) os.chdir(final_directory) - for folder in ['freqBuckshot', 'normalized', 'unnormalized']: + for folder in ["freqBuckshot", "normalized", "unnormalized"]: new_directory = os.path.join(final_directory, folder) if not os.path.exists(new_directory): os.makedirs(new_directory) os.chdir(new_directory) - if 'freq' in folder: - for tech in ['sky90', 'sky130', 'tsmc28', 'tsmc28psyn']: + if "freq" in folder: + for tech in ["sky90", "sky130", "tsmc28", "tsmc28psyn"]: for mod in modules: tech_directory = os.path.join(new_directory, tech) mod_directory = os.path.join(tech_directory, mod) if not os.path.exists(mod_directory): os.makedirs(mod_directory) - os.chdir('..') - + os.chdir("..") + os.chdir(current_directory) - -if __name__ == '__main__': + + +if __name__ == "__main__": ############################## # set up stuff, global variables - widths = [64, 128] - modules = ['adder', 'comparator'] + widths = [8, 16, 32, 64, 128] + modules = ["adder"] - normAddWidth = 32 # divisor to use with N since normalizing to add_32 + normAddWidth = 32 # divisor to use with N since normalizing to add_32 - fitDict = {'adder': ['cg', 'l', 'l'], 'mul': ['cg', 's', 's'], 'comparator': ['cg', 'l', 'l'], 'csa': ['c', 'l', 'l'], 'shifter': ['cg', 'l', 'ln'], 'flop': ['c', 'l', 'l'], 'binencoder': ['cg', 'l', 'l']} - fitDict.update(dict.fromkeys(['mux2', 'mux4', 'mux8'], ['cg', 'l', 'l'])) + fitDict = { + "adder": ["cg", "l", "l"], + "mul": ["cg", "s", "s"], + "comparator": ["cg", "l", "l"], + "csa": ["c", "l", "l"], + "shifter": ["cg", "l", "ln"], + "flop": ["c", "l", "l"], + "binencoder": ["cg", "l", "l"], + } + fitDict.update(dict.fromkeys(["mux2", "mux4", "mux8"], ["cg", "l", "l"])) - TechSpec = namedtuple("TechSpec", "tech color shape delay area lpower denergy") - techSpecs = [['sky90', 'green', 'o', 43.2e-3, 1440.600027, 714.057, 0.658022690438], ['sky130', 'red', 'o', 43.2e-3, 1440.600027, 714.057, 0.658022690438], ['tsmc28', 'blue', '^', 12.2e-3, 209.286002, 1060.0, .08153281695882594], ['tsmc28psyn', 'blue', '^', 12.2e-3, 209.286002, 1060.0, .08153281695882594]] - techSpecs = [TechSpec(*t) for t in techSpecs] - combined = TechSpec('combined fit', 'red', '_', 0, 0, 0, 0) + TechSpec = namedtuple("TechSpec", "tech color shape delay area lpower denergy") + # FO4 delay information information + techSpecs = [ + #["sky90", "green", "o", 43.2e-3, 1440.600027, 714.057, 0.658022690438], + # Area/Lpower/Denergy needs to be corrected here (jes) + ["sky130", "orange", "o", 99.5e-3, 1440.600027, 714.057, 0.658022690438], + # ["tsmc28", "blue", "^", 12.2e-3, 209.286002, 1060.0, 0.08153281695882594], + # ["tsmc28psyn", "blue", "^", 12.2e-3, 209.286002, 1060.0, 0.08153281695882594], + ] + techSpecs = [TechSpec(*t) for t in techSpecs] + combined = TechSpec("combined fit", "red", "_", 0, 0, 0, 0) ############################## # cleanup() # run to remove garbage synth runs - synthsintocsv() # slow, run only when new synth runs to add to csv - - allSynths = synthsfromcsv('ppaData.csv') # your csv here! - bestSynths = csvOfBest('bestSynths.csv') - makePlotDirectory() + synthsintocsv() # slow, run only when new synth runs to add to csv + + allSynths = synthsfromcsv("ppaData.csv") # your csv here! + bestSynths = csvOfBest("bestSynths.csv") + makePlotDirectory() # ### other functions # makeCoefTable() @@ -734,12 +923,12 @@ if __name__ == '__main__': # muxPlot() # stdDevError() - for mod in modules: - for w in widths: - #freqPlot('sky90', mod, w) - freqPlot('sky130', mod, w) - #freqPlot('tsmc28', mod, w) - #freqPlot('tsmc28psyn', mod, w) - #plotPPA(mod, widths, norm=False) - #plotPPA(mod, aleOpt=True) - plt.close('all') + for mod in modules: + for w in widths: + #freqPlot('sky90', mod, w) + freqPlot("sky130", mod, w) + # freqPlot('tsmc28', mod, w) + # freqPlot('tsmc28psyn', mod, w) + plotPPA(mod, norm=False) + plotPPA(mod, aleOpt=True) + plt.close("all") diff --git a/synthDC/ppa/ppaSynth.py b/synthDC/ppa/ppaSynth.py index ceb6edbd2..30fe1254f 100755 --- a/synthDC/ppa/ppaSynth.py +++ b/synthDC/ppa/ppaSynth.py @@ -12,11 +12,11 @@ from ppaAnalyze import synthsfromcsv def runCommand(module, width, tech, freq): command = "make synth DESIGN={} WIDTH={} TECH={} DRIVE=INV FREQ={} MAXOPT=1 MAXCORES=1".format(module, width, tech, freq) - subprocess.Popen(command, shell=True) + subprocess.call(command, shell=True) def deleteRedundant(synthsToRun): '''removes any previous runs for the current synthesis specifications''' - synthStr = "rm -rf runs/ppa_{}_{}_rv32e_{}nm_{}_*" + synthStr = "rm -rf runs/{}_{}_rv32e_{}_{}_*" for synth in synthsToRun: bashCommand = synthStr.format(*synth) outputCPL = subprocess.check_output(['bash','-c', bashCommand]) @@ -46,7 +46,7 @@ def freqModuleSweep(widths, modules, tech): return synthsToRun def filterRedundant(synthsToRun): - bashCommand = "find . -path '*runs/ppa*rv32e*' -prune" + bashCommand = "find . -path '*runs/*' -prune" output = subprocess.check_output(['bash','-c', bashCommand]) specReg = re.compile('[a-zA-Z0-9]+') allSynths = output.decode("utf-8").split('\n')[:-1] @@ -84,14 +84,15 @@ if __name__ == '__main__': synthsToRun = freqSweep(module, width, tech) ##### Run a sweep for multiple modules/widths based on best delay found in existing syntheses - modules = ['adder', 'comparator'] - widths = [64, 128] + modules = ['adder'] + widths = [8, 16, 32, 64, 128] tech = 'sky130' synthsToRun = freqModuleSweep(widths, modules, tech) ##### Only do syntheses for which a run doesn't already exist - synthsToRun = filterRedundant(synthsToRun) - + synthsToRun = filterRedundant(synthsToRun) pool = Pool(processes=25) -pool.starmap(runCommand, synthsToRun) \ No newline at end of file +pool.starmap(runCommand, synthsToRun) +pool.close() +pool.join() \ No newline at end of file