mirror of
				https://github.com/openhwgroup/cvw
				synced 2025-02-11 06:05:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			936 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			936 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python3
 | 
						|
#
 | 
						|
# Python regression test for DC
 | 
						|
# Madeleine Masser-Frye mmasserfrye@hmc.edu 5/22
 | 
						|
# James Stine james.stine@okstate.edu 15 October 2023
 | 
						|
#
 | 
						|
 | 
						|
import scipy.optimize as opt
 | 
						|
import subprocess
 | 
						|
import csv
 | 
						|
import re
 | 
						|
from matplotlib.cbook import flatten
 | 
						|
import matplotlib.pyplot as plt
 | 
						|
import matplotlib.lines as lines
 | 
						|
import matplotlib as mpl
 | 
						|
import numpy as np
 | 
						|
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:
 | 
						|
        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
 | 
						|
            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
 | 
						|
    """
 | 
						|
    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]
 | 
						|
 | 
						|
    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)",
 | 
						|
        ]
 | 
						|
    )
 | 
						|
 | 
						|
    for oneSynth in allSynths:
 | 
						|
        module, width, risc, tech, freq = specReg.findall(oneSynth)[1:6]
 | 
						|
        tech = tech[:-2]  
 | 
						|
        metrics = []
 | 
						|
        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:
 | 
						|
                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]
 | 
						|
        area = metrics[1]
 | 
						|
        lpower = metrics[4]
 | 
						|
        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]]
 | 
						|
 | 
						|
        writer.writerow([module, tech, width, freq, delay, area, lpower, denergy])
 | 
						|
    file.close()
 | 
						|
 | 
						|
 | 
						|
def cleanup():
 | 
						|
    """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]
 | 
						|
        for run in allSynths:
 | 
						|
            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]
 | 
						|
    for oneSynth in allSynths:
 | 
						|
        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])
 | 
						|
    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
 | 
						|
    """
 | 
						|
 | 
						|
    if width != None:
 | 
						|
        widthsToGet = width
 | 
						|
    else:
 | 
						|
        widthsToGet = widths
 | 
						|
 | 
						|
    metric = []
 | 
						|
    widthL = []
 | 
						|
 | 
						|
    if freq != None:
 | 
						|
        for oneSynth in allSynths:
 | 
						|
            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
 | 
						|
    else:
 | 
						|
        for w in widthsToGet:
 | 
						|
            for oneSynth in bestSynths:
 | 
						|
                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
 | 
						|
                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
 | 
						|
                        ):
 | 
						|
                            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)",
 | 
						|
        ]
 | 
						|
    )
 | 
						|
    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
 | 
						|
    """
 | 
						|
 | 
						|
    coefsr = [str(sigfig(c, 2)) for c in coefs]
 | 
						|
    if ale:
 | 
						|
        if normAddWidth == 32:
 | 
						|
            sub = "S"
 | 
						|
        elif normAddWidth != 1:
 | 
						|
            print("Equations are wrong, check normAddWidth")
 | 
						|
    else:
 | 
						|
        sub = "N"
 | 
						|
 | 
						|
    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]
 | 
						|
            ind += 1
 | 
						|
 | 
						|
    eq = eq[3:]  # chop off leading ' + '
 | 
						|
 | 
						|
    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)),
 | 
						|
            )
 | 
						|
        ]
 | 
						|
        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
 | 
						|
    """
 | 
						|
    singlePlot = True
 | 
						|
    if ax or (freq == 10):
 | 
						|
        singlePlot = False
 | 
						|
    if ax is None:
 | 
						|
        ax = plt.gca()
 | 
						|
 | 
						|
    fullLeg = []
 | 
						|
    allWidths = []
 | 
						|
    allMetrics = []
 | 
						|
 | 
						|
    ale = var != "delay"  # if not delay, must be area, leakage, or energy
 | 
						|
    modFit = fitDict[module]
 | 
						|
    fits = modFit[ale]
 | 
						|
 | 
						|
    if freq:
 | 
						|
        ls = "--"
 | 
						|
    else:
 | 
						|
        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]
 | 
						|
 | 
						|
        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
 | 
						|
            ax.scatter(widths, metric, color=c, marker=spec.shape)
 | 
						|
            ax.plot(xp, pred, color=c, linestyle=ls)
 | 
						|
            allWidths += widths
 | 
						|
            allMetrics += metric
 | 
						|
 | 
						|
        # 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)",
 | 
						|
        }
 | 
						|
    else:
 | 
						|
        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"):
 | 
						|
        ax.set_xlabel("Width (bits)")
 | 
						|
    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"
 | 
						|
        ax.add_artist(ax.legend(handles=fullLeg, loc=legLoc))
 | 
						|
        titleStr = (
 | 
						|
            "  (target  " + str(freq) + "MHz)"
 | 
						|
            if freq != None
 | 
						|
            else " (best achievable delay)"
 | 
						|
        )
 | 
						|
        ax.set_title(module + titleStr)
 | 
						|
        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
 | 
						|
    """
 | 
						|
    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)
 | 
						|
    xpToCalc = xp
 | 
						|
 | 
						|
    if ale:
 | 
						|
        widths = [w / normAddWidth for w in widths]
 | 
						|
        xpToCalc = [x / normAddWidth for x in xp]
 | 
						|
 | 
						|
    mat = []
 | 
						|
    for w in widths:
 | 
						|
        row = []
 | 
						|
        for func in funcArr:
 | 
						|
            row += [func(w)]
 | 
						|
        mat += [row]
 | 
						|
 | 
						|
    # var = [0, 1, 2, 3, 4]
 | 
						|
    y = np.array(var, dtype=np.float64)
 | 
						|
    coefs = opt.nnls(mat, y)[0]
 | 
						|
 | 
						|
    yp = []
 | 
						|
    for w in widths:
 | 
						|
        n = [func(w) for func in funcArr]
 | 
						|
        yp += [sum(np.multiply(coefs, n))]
 | 
						|
    r2 = skm.r2_score(y, yp)
 | 
						|
 | 
						|
    pred = []
 | 
						|
    for x in xpToCalc:
 | 
						|
        n = [func(x) for func in funcArr]
 | 
						|
        pred += [sum(np.multiply(coefs, n))]
 | 
						|
 | 
						|
    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)
 | 
						|
    """
 | 
						|
    file = open("ppaFitting.csv", "w")
 | 
						|
    writer = csv.writer(file)
 | 
						|
    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"
 | 
						|
                metL = []
 | 
						|
                modFit = fitDict[module]
 | 
						|
                fits = modFit[ale]
 | 
						|
 | 
						|
                for spec in techSpecs:
 | 
						|
                    metric = getVals(spec.tech, module, var, freq=freq)
 | 
						|
                    techdict = spec._asdict()
 | 
						|
                    norm = techdict[var]
 | 
						|
                    metL += [m / norm for m in metric]
 | 
						|
 | 
						|
                xp, pred, coefs, r2 = regress(widths * 2, metL, fits, ale)
 | 
						|
                coefs = np.ndarray.tolist(coefs)
 | 
						|
                coefsToWrite = [None] * 5
 | 
						|
                fitTerms = "clsgn"
 | 
						|
                ind = 0
 | 
						|
                for i in range(len(fitTerms)):
 | 
						|
                    if fitTerms[i] in fits:
 | 
						|
                        coefsToWrite[i] = coefs[ind]
 | 
						|
                        ind += 1
 | 
						|
                row = [module, var, target] + coefsToWrite + [r2]
 | 
						|
                writer.writerow(row)
 | 
						|
 | 
						|
    file.close()
 | 
						|
 | 
						|
 | 
						|
def sigfig(num, 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)
 | 
						|
    """
 | 
						|
    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",
 | 
						|
        ]
 | 
						|
    )
 | 
						|
 | 
						|
    for module in modules:
 | 
						|
        eqs = []
 | 
						|
        for freq in [None, 10]:
 | 
						|
            for var in ["delay", "area", "lpower", "denergy"]:
 | 
						|
                if (var == "delay") and (freq == 10):
 | 
						|
                    pass
 | 
						|
                else:
 | 
						|
                    ale = var != "delay"
 | 
						|
                    metL = []
 | 
						|
                    modFit = fitDict[module]
 | 
						|
                    fits = modFit[ale]
 | 
						|
 | 
						|
                    for spec in techSpecs:
 | 
						|
                        metric = getVals(spec.tech, module, var, freq=freq)
 | 
						|
                        techdict = spec._asdict()
 | 
						|
                        norm = techdict[var]
 | 
						|
                        metL += [m / norm for m in metric]
 | 
						|
 | 
						|
                    xp, pred, coefs, r2 = regress(widths * 2, metL, fits, ale)
 | 
						|
                    coefs = np.ndarray.tolist(coefs)
 | 
						|
                    eqs += [genLegend(fits, coefs, ale=ale)]
 | 
						|
        row = [module] + eqs
 | 
						|
        writer.writerow(row)
 | 
						|
 | 
						|
    file.close()
 | 
						|
 | 
						|
 | 
						|
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:
 | 
						|
        funcArr += [lambda x: 1]
 | 
						|
    if "l" in fits:
 | 
						|
        funcArr += [lambda x: x]
 | 
						|
    if "s" in fits:
 | 
						|
        funcArr += [lambda x: x**2]
 | 
						|
    if "g" in fits:
 | 
						|
        funcArr += [lambda 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 = []
 | 
						|
    for i in range(len(freqs)):
 | 
						|
        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"""
 | 
						|
 | 
						|
    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
 | 
						|
            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")
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
        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"),
 | 
						|
    ]
 | 
						|
 | 
						|
    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):
 | 
						|
        width = mod
 | 
						|
        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"""
 | 
						|
    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
 | 
						|
            freqsL[ind] += [oneSynth.freq]
 | 
						|
            delaysL[ind] += [oneSynth.delay]
 | 
						|
            areasL[ind] += [oneSynth.area]
 | 
						|
 | 
						|
    f, (ax1) = plt.subplots(1, 1)
 | 
						|
    ax2 = ax1.twinx()
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
        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)"
 | 
						|
        ),
 | 
						|
    ]
 | 
						|
 | 
						|
    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))
 | 
						|
 | 
						|
    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
 | 
						|
    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="--")
 | 
						|
 | 
						|
    plt.savefig("./plots/squareareadelay_" + mod + "_" + str(width) + ".png")
 | 
						|
    # plt.show()
 | 
						|
 | 
						|
 | 
						|
def squarify(fig):
 | 
						|
    """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.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.0 - axs / h) / 2
 | 
						|
        fig.subplots_adjust(bottom=l, top=1 - l)
 | 
						|
 | 
						|
 | 
						|
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"]]
 | 
						|
 | 
						|
    freqs = [freq]
 | 
						|
    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):
 | 
						|
                    pass
 | 
						|
                else:
 | 
						|
                    # 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"):
 | 
						|
                axs[i, j].set_ylim(ymin=0)
 | 
						|
                ytop = axs[i, j].get_ylim()[1]
 | 
						|
                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"
 | 
						|
        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)
 | 
						|
    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="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
 | 
						|
    """
 | 
						|
    ax = plt.gca()
 | 
						|
 | 
						|
    inputs = [2, 4, 8]
 | 
						|
    allInputs = inputs * 2
 | 
						|
    fullLeg = []
 | 
						|
 | 
						|
    for crit in ["data", "control"]:
 | 
						|
        allMetrics = []
 | 
						|
        muxes = ["mux2", "mux4", "mux8"]
 | 
						|
 | 
						|
        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])
 | 
						|
 | 
						|
            if norm:
 | 
						|
                techdict = spec._asdict()
 | 
						|
                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
 | 
						|
                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.set_xticks(inputs)
 | 
						|
    ax.set_xlabel("Number of inputs")
 | 
						|
    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"]:
 | 
						|
        errlist = []
 | 
						|
        for module in modules:
 | 
						|
            ale = var != "delay"
 | 
						|
            metL = []
 | 
						|
            modFit = fitDict[module]
 | 
						|
            fits = modFit[ale]
 | 
						|
            funcArr = genFuncs(fits)
 | 
						|
 | 
						|
            for spec in techSpecs:
 | 
						|
                metric = getVals(spec.tech, module, var)
 | 
						|
                techdict = spec._asdict()
 | 
						|
                norm = techdict[var]
 | 
						|
                metL += [m / norm for m in metric]
 | 
						|
 | 
						|
            if ale:
 | 
						|
                ws = [w / normAddWidth for w in widths]
 | 
						|
            else:
 | 
						|
                ws = widths
 | 
						|
            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]
 | 
						|
 | 
						|
            yp = []
 | 
						|
            for w in ws:
 | 
						|
                n = [func(w) for func in funcArr]
 | 
						|
                yp += [sum(np.multiply(coefs, n))]
 | 
						|
 | 
						|
            if (var == "delay") & (module == "flop"):
 | 
						|
                pass
 | 
						|
            elif (module == "mult") & ale:
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                for i in range(len(y)):
 | 
						|
                    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)
 | 
						|
 | 
						|
 | 
						|
def makePlotDirectory():
 | 
						|
    """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")
 | 
						|
    if not os.path.exists(final_directory):
 | 
						|
        os.makedirs(final_directory)
 | 
						|
    os.chdir(final_directory)
 | 
						|
 | 
						|
    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"]:
 | 
						|
                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(current_directory)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    ##############################
 | 
						|
    # set up stuff, global variables
 | 
						|
    widths = [8, 16, 32, 64, 128]
 | 
						|
    modules = ["adder"]
 | 
						|
 | 
						|
    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"]))
 | 
						|
 | 
						|
    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()
 | 
						|
 | 
						|
    # ### other functions
 | 
						|
    # makeCoefTable()
 | 
						|
    # makeEqTable()
 | 
						|
    # 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, norm=False)
 | 
						|
            plotPPA(mod, aleOpt=True)
 | 
						|
            plt.close("all")
 |