From a55b766c44bf757bf6cfc80a84a61832db9548a0 Mon Sep 17 00:00:00 2001 From: John McMaster Date: Tue, 11 Sep 2018 19:45:33 -0700 Subject: [PATCH] timfuz: track corners properly Signed-off-by: John McMaster --- fuzzers/007-timing/csv_flat2group.py | 12 ++--- fuzzers/007-timing/csv_group2flat.py | 34 ++++++------ fuzzers/007-timing/projects/project.mk | 9 ++-- fuzzers/007-timing/solve_leastsq.py | 20 +++----- fuzzers/007-timing/solve_linprog.py | 71 +++++++++++--------------- fuzzers/007-timing/tile_annotate.py | 22 ++++---- fuzzers/007-timing/timfuz.py | 33 ++++++++++-- fuzzers/007-timing/timfuz_solve.py | 2 +- 8 files changed, 111 insertions(+), 92 deletions(-) diff --git a/fuzzers/007-timing/csv_flat2group.py b/fuzzers/007-timing/csv_flat2group.py index 57be54a5..f1398b87 100644 --- a/fuzzers/007-timing/csv_flat2group.py +++ b/fuzzers/007-timing/csv_flat2group.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 -from timfuz import Benchmark, loadc_Ads_b, index_names, load_sub, run_sub_json, instances +from timfuz import Benchmark, loadc_Ads_bs, index_names, load_sub, run_sub_json, instances def gen_group(fnin, sub_json, strict=False, verbose=False): print('Loading data') - # FIXME: preserve corners - Ads, b = loadc_Ads_b([fnin], corner=None, ico=True) + Ads, bs = loadc_Ads_bs([fnin], ico=True) print('Sub: %u rows' % len(Ads)) iold = instances(Ads) @@ -15,10 +14,10 @@ def gen_group(fnin, sub_json, strict=False, verbose=False): print("Sub: %u => %u names" % (len(names_old), len(names))) print('Sub: %u => %u instances' % (iold, instances(Ads))) - for row_ds, row_b in zip(Ads, b): - yield row_ds, [row_b for _ in range(4)] + for row_ds, row_bs in zip(Ads, bs): + yield row_ds, row_bs -def run(fns_in, fnout, sub_json, corner=None, strict=False, verbose=False): +def run(fns_in, fnout, sub_json, strict=False, verbose=False): with open(fnout, 'w') as fout: fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n') for fn_in in fns_in: @@ -38,7 +37,6 @@ def main(): ) parser.add_argument('--verbose', action='store_true', help='') - parser.add_argument('--massage', action='store_true', help='') parser.add_argument('--strict', action='store_true', help='') parser.add_argument('--sub-csv', help='') parser.add_argument('--sub-json', required=True, help='Group substitutions to make fully ranked') diff --git a/fuzzers/007-timing/csv_group2flat.py b/fuzzers/007-timing/csv_group2flat.py index bf5c2870..2449b916 100644 --- a/fuzzers/007-timing/csv_group2flat.py +++ b/fuzzers/007-timing/csv_group2flat.py @@ -2,7 +2,7 @@ # https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html from scipy.optimize import linprog -from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, simplify_rows, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json, A_ub_np2d, print_eqns, print_eqns_np, Ads2bounds, loadc_Ads_raw +from timfuz import Benchmark, loadc_Ads_bs, load_sub, Ads2bounds, corners2csv, corner_s2i from timfuz_massage import massage_equations import numpy as np import glob @@ -15,15 +15,13 @@ import datetime import os import time -def gen_flat(fnin, sub_json): - # FIXME: preserve all bounds - # Ads, bs = loadc_Ads_raw([csv_fn_in]) - Ads, b = loadc_Ads_b([fnin], corner=None, ico=True) - bounds = Ads2bounds(Ads, b) +def gen_flat(fnin, sub_json, corner=None): + Ads, bs = loadc_Ads_bs([fnin], ico=True) + bounds = Ads2bounds(Ads, bs) zeros = set() nonzeros = set() - for bound_name, bound_b in bounds.items(): + for bound_name, bound_bs in bounds.items(): sub = sub_json['subs'].get(bound_name, None) if sub: # put entire delay into pivot @@ -34,18 +32,23 @@ def gen_flat(fnin, sub_json): #for name in non_pivot: # assert name not in nonzeros, (pivot, name, nonzeros) zeros.update(non_pivot) - yield pivot, bound_b + yield pivot, bound_bs else: nonzeros.add(bound_name) - yield bound_name, bound_b + yield bound_name, bound_bs # non-pivots can appear multiple times, but they should always be zero # however, due to substitution limitations, just warn violations = zeros.intersection(nonzeros) if len(violations): print('WARNING: %s non-0 non-pivot' % (len(violations))) - for zero in zeros - violations: - yield zero, 0 + # XXX: how to best handle these? + # should they be fixed 0? + if corner: + zero_row = [None, None, None, None] + zero_row[corner_s2i[corner]] = 0 + for zero in zeros - violations: + yield zero, zero_row def run(fnin, fnout, sub_json, corner=None, sort=False, verbose=False): if sort: @@ -55,10 +58,10 @@ def run(fnin, fnout, sub_json, corner=None, sort=False, verbose=False): with open(fnout, 'w') as fout: fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n') - for name, delay in sortf(gen_flat(fnin, sub_json)): + #for name, corners in sortf(gen_flat(fnin, sub_json)): + for name, corners in gen_flat(fnin, sub_json, corner=corner): row_ico = 1 - corners = [delay for _ in range(4)] - items = [str(row_ico), ' '.join([str(x) for x in corners])] + items = [str(row_ico), corners2csv(corners)] items.append('%u %s' % (1, name)) fout.write(','.join(items) + '\n') @@ -74,6 +77,7 @@ def main(): parser.add_argument('--sort', action='store_true', help='') parser.add_argument('--sub-csv', help='') parser.add_argument('--sub-json', required=True, help='Group substitutions to make fully ranked') + parser.add_argument('--corner', default=None, help='') parser.add_argument('fnin', default=None, help='input timing delay .csv') parser.add_argument('fnout', default=None, help='output timing delay .csv') args = parser.parse_args() @@ -83,7 +87,7 @@ def main(): sub_json = load_sub(args.sub_json) try: - run(args.fnin, args.fnout, sub_json=sub_json, sort=args.sort, verbose=args.verbose) + run(args.fnin, args.fnout, sub_json=sub_json, sort=args.sort, verbose=args.verbose, corner=args.corner) finally: print('Exiting after %s' % bench) diff --git a/fuzzers/007-timing/projects/project.mk b/fuzzers/007-timing/projects/project.mk index f870af11..45a6165c 100644 --- a/fuzzers/007-timing/projects/project.mk +++ b/fuzzers/007-timing/projects/project.mk @@ -3,6 +3,7 @@ SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N))) SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS)) CSVS := $(addsuffix /timing3.csv,$(SPECIMENS)) TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing +CORNER=slow_max all: build/tilea.json @@ -26,7 +27,7 @@ build/sub.json: $(SPECIMENS_OK) mkdir -p build # Discover which variables can be separated # This is typically the longest running operation - python3 $(TIMFUZ_DIR)/rref.py --simplify --out build/sub.json.tmp $(CSVS) + python3 $(TIMFUZ_DIR)/rref.py --corner $(CORNER) --simplify --out build/sub.json.tmp $(CSVS) mv build/sub.json.tmp build/sub.json build/grouped.csv: $(SPECIMENS_OK) build/sub.json @@ -41,17 +42,17 @@ build/checksub: build/grouped.csv build/sub.json build/leastsq.csv: build/sub.json build/grouped.csv build/checksub # Create a rough timing model that approximately fits the given paths - python3 $(TIMFUZ_DIR)/solve_leastsq.py --sub-json build/sub.json build/grouped.csv --out build/leastsq.csv.tmp + python3 $(TIMFUZ_DIR)/solve_leastsq.py --sub-json build/sub.json build/grouped.csv --corner $(CORNER) --out build/leastsq.csv.tmp mv build/leastsq.csv.tmp build/leastsq.csv build/linprog.csv: build/leastsq.csv build/grouped.csv # Tweak rough timing model, making sure all constraints are satisfied - python3 $(TIMFUZ_DIR)/solve_linprog.py --sub-json build/sub.json --sub-csv build/leastsq.csv --massage build/grouped.csv --out build/linprog.csv.tmp + python3 $(TIMFUZ_DIR)/solve_linprog.py --sub-json build/sub.json --sub-csv build/leastsq.csv --massage build/grouped.csv --corner $(CORNER) --out build/linprog.csv.tmp mv build/linprog.csv.tmp build/linprog.csv build/flat.csv: build/linprog.csv # Take separated variables and back-annotate them to the original timing variables - python3 $(TIMFUZ_DIR)/csv_group2flat.py --sub-json build/sub.json --sort build/linprog.csv build/flat.csv.tmp + python3 $(TIMFUZ_DIR)/csv_group2flat.py --sub-json build/sub.json --corner $(CORNER) --sort build/linprog.csv build/flat.csv.tmp mv build/flat.csv.tmp build/flat.csv build/tilea.json: build/flat.csv diff --git a/fuzzers/007-timing/solve_leastsq.py b/fuzzers/007-timing/solve_leastsq.py index bbb20ecc..10cacbe3 100644 --- a/fuzzers/007-timing/solve_leastsq.py +++ b/fuzzers/007-timing/solve_leastsq.py @@ -2,20 +2,16 @@ # https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html from scipy.optimize import linprog -from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, simplify_rows, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json, A_ub_np2d, print_eqns, print_eqns_np -from timfuz_massage import massage_equations +from timfuz import Benchmark, load_sub, corner_s2i, acorner2csv +import timfuz import numpy as np import glob -import json import math -from collections import OrderedDict from fractions import Fraction import sys -import datetime import os import time import timfuz_solve -import numpy import scipy.optimize as optimize from scipy.optimize import least_squares @@ -37,10 +33,11 @@ def mkestimate(Anp, b): x0[coli] = min(x0[coli], ub) return x0 -def save(outfn, res, names): +def save(outfn, res, names, corner): # ballpark minimum actual observed delay is around 7 (carry chain) # anything less than one is probably a solver artifact delta = 0.5 + corneri = corner_s2i[corner] print('Writing resutls') skips = 0 @@ -59,14 +56,13 @@ def save(outfn, res, names): skips += 1 continue #xvali = round(xval) - xvali = math.ceil(xval) - corners = [xvali for _ in range(4)] - items = [str(row_ico), ' '.join([str(x) for x in corners])] + + items = [str(row_ico), acorner2csv(math.ceil(xval), corneri)] items.append('%u %s' % (1, name)) fout.write(','.join(items) + '\n') print('Wrote: skip %u => %u / %u valid delays' % (skips, len(names) - skips, len(names))) -def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None): +def run_corner(Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None): # Given timing scores for above delays (-ps) assert type(Anp[0]) is np.ndarray, type(Anp[0]) assert type(b) is np.ndarray, type(b) @@ -135,7 +131,7 @@ def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None): print('Done') if outfn: - save(outfn, res, names) + save(outfn, res, names, corner) def main(): import argparse diff --git a/fuzzers/007-timing/solve_linprog.py b/fuzzers/007-timing/solve_linprog.py index c33cd34a..e8fa4ac1 100644 --- a/fuzzers/007-timing/solve_linprog.py +++ b/fuzzers/007-timing/solve_linprog.py @@ -2,21 +2,44 @@ # https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html from scipy.optimize import linprog -from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, simplify_rows, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json, A_ub_np2d, print_eqns, print_eqns_np -from timfuz_massage import massage_equations +from timfuz import Benchmark, load_sub, A_ub_np2d, acorner2csv, corner_s2i import numpy as np import glob import json import math -from collections import OrderedDict -from fractions import Fraction import sys -import datetime import os import time import timfuz_solve -def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None): +def save(outfn, res, names, corner): + # ballpark minimum actual observed delay is around 7 (carry chain) + # anything less than one is probably a solver artifact + delta = 0.5 + corneri = corner_s2i[corner] + + print('Writing resutls') + zeros = 0 + with open(outfn, 'w') as fout: + # write as one variable per line + # this natively forms a bound if fed into linprog solver + fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n') + for xval, name in zip(res.x, names): + row_ico = 1 + + # FIXME: only report for the given corner? + # also review ceil vs floor choice for min vs max + # lets be more conservative for now + if xval < delta: + print('WARNING: near 0 delay on %s: %0.6f' % (name, xval)) + zeros += 1 + #continue + items = [str(row_ico), acorner2csv(math.ceil(xval), corneri)] + items.append('%u %s' % (1, name)) + fout.write(','.join(items) + '\n') + print('Wrote: zeros %u => %u / %u constrained delays' % (zeros, len(names) - zeros, len(names))) + +def run_corner(Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None): # Given timing scores for above delays (-ps) assert type(Anp[0]) is np.ndarray, type(Anp[0]) assert type(b) is np.ndarray, type(b) @@ -41,12 +64,6 @@ def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None): #A_ub = -1.0 * Anp A_ub = [-1.0 * x for x in Anp] - if verbose: - print('') - print('A_ub b_ub') - print_eqns_np(A_ub, b_ub, verbose=verbose) - print('') - print('Creating misc constants...') # Minimization function scalars # Treat all logic elements as equally important @@ -107,33 +124,7 @@ def run_corner(Anp, b, names, verbose=False, opts={}, meta={}, outfn=None): print('Delay on %d / %d' % (nonzeros, len(res.x))) if outfn: - # ballpark minimum actual observed delay is around 7 (carry chain) - # anything less than one is probably a solver artifact - delta = 0.5 - - print('Writing resutls') - zeros = 0 - with open(outfn, 'w') as fout: - # write as one variable per line - # this natively forms a bound if fed into linprog solver - fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n') - for xval, name in zip(res.x, names): - row_ico = 1 - - # FIXME: only report for the given corner? - # also review ceil vs floor choice for min vs max - # lets be more conservative for now - if xval < delta: - print('WARNING: near 0 delay on %s: %0.6f' % (name, xval)) - zeros += 1 - #continue - #xvali = round(xval) - xvali = math.ceil(xval) - corners = [xvali for _ in range(4)] - items = [str(row_ico), ' '.join([str(x) for x in corners])] - items.append('%u %s' % (1, name)) - fout.write(','.join(items) + '\n') - print('Wrote: zeros %u => %u / %u constrained delays' % (zeros, len(names) - zeros, len(names))) + save(outfn, res, names, corner) def main(): import argparse @@ -147,7 +138,7 @@ def main(): parser.add_argument('--massage', action='store_true', help='') parser.add_argument('--sub-csv', help='') parser.add_argument('--sub-json', help='Group substitutions to make fully ranked') - parser.add_argument('--corner', default="slow_max", help='') + parser.add_argument('--corner', default=None, required=True, help='') parser.add_argument('--out', default=None, help='output timing delay .json') parser.add_argument( 'fns_in', diff --git a/fuzzers/007-timing/tile_annotate.py b/fuzzers/007-timing/tile_annotate.py index 005b6c52..5b897b4d 100644 --- a/fuzzers/007-timing/tile_annotate.py +++ b/fuzzers/007-timing/tile_annotate.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import timfuz -from timfuz import loadc_Ads_b, Ads2bounds +from timfuz import loadc_Ads_bs, Ads2bounds import sys import os @@ -17,37 +17,41 @@ def run(fnin, fnout, tile_json_fn, verbose=False): tilej = json.load(open(tile_json_fn, 'r')) # FIXME: all corners - Ads, b = loadc_Ads_b([fnin], corner=None, ico=True) - bounds = Ads2bounds(Ads, b) + Ads, bs = loadc_Ads_bs([fnin], ico=True) + bounds = Ads2bounds(Ads, bs) pipn_net = 0 pipn_solved = [0, 0, 0, 0] + pipn_covered = [0, 0, 0, 0] wiren_net = 0 wiren_solved = [0, 0, 0, 0] + wiren_covered = [0, 0, 0, 0] for tile in tilej['tiles'].values(): pips = tile['pips'] for k, v in pips.items(): - val = bounds.get('PIP_' + v, None) - pips[k] = quad(val) + pips[k] = bounds.get('PIP_' + v, [None, None, None, None]) pipn_net += 1 for i in range(4): if pips[k][i]: pipn_solved[i] += 1 + if pips[k][i] is not None: + pipn_covered[i] += 1 wires = tile['wires'] for k, v in wires.items(): - val = bounds.get('WIRE_' + v, None) - wires[k] = quad(val) + wires[k] = bounds.get('WIRE_' + v, [None, None, None, None]) wiren_net += 1 for i in range(4): if wires[k][i]: wiren_solved[i] += 1 + if wires[k][i] is not None: + wiren_covered[i] += 1 for corner, corneri in timfuz.corner_s2i.items(): print('Corner %s' % corner) - print(' Pips: %u / %u solved' % (pipn_solved[corneri], pipn_net)) - print(' Wires: %u / %u solved' % (wiren_solved[corneri], wiren_net)) + print(' Pips: %u / %u solved, %u / %u covered' % (pipn_solved[corneri], pipn_net, pipn_covered[corneri], pipn_net)) + print(' Wires: %u / %u solved, %u / %u covered' % (wiren_solved[corneri], wiren_net, wiren_covered[corneri], wiren_net)) json.dump(tilej, open(fnout, 'w'), sort_keys=True, indent=4, separators=(',', ': ')) diff --git a/fuzzers/007-timing/timfuz.py b/fuzzers/007-timing/timfuz.py index 09b34da2..c20a1b57 100644 --- a/fuzzers/007-timing/timfuz.py +++ b/fuzzers/007-timing/timfuz.py @@ -530,7 +530,12 @@ def loadc_Ads_mkb(fns, mkb, filt): corners = cols[1] vars = cols[2:] - corners = [int(x) for x in corners.split()] + def mkcorner(bstr): + if bstr == 'None': + return None + else: + return int(bstr) + corners = [mkcorner(corner) for corner in corners.split()] def mkvar(x): i, var = x.split() return (var, int(i)) @@ -556,6 +561,16 @@ def loadc_Ads_b(fns, corner, ico=None): return val[corneri] return loadc_Ads_mkb(fns, mkb, filt) +def loadc_Ads_bs(fns, ico=None): + if ico is not None: + filt = lambda ico_, corners, vars: ico_ == ico + else: + filt = lambda ico_, corners, vars: True + + def mkb(val): + return val + return loadc_Ads_mkb(fns, mkb, filt) + def loadc_Ads_raw(fns): filt = lambda ico, corners, vars: True @@ -699,13 +714,13 @@ def print_eqns_np(A_ub, b_ub, verbose=0): Adi = A_ub_np2d(A_ub) print_eqns(Adi, b_ub, verbose=verbose) -def Ads2bounds(Ads, b): +def Ads2bounds(Ads, bs): ret = {} - for row_ds, row_b in zip(Ads, b): + for row_ds, row_bs in zip(Ads, bs): assert len(row_ds) == 1 k, v = list(row_ds.items())[0] assert v == 1 - ret[k] = row_b + ret[k] = row_bs return ret def instances(Ads): @@ -713,3 +728,13 @@ def instances(Ads): for row_ds in Ads: ret += sum(row_ds.values()) return ret + +def acorner2csv(b, corneri): + corners = ["None" for _ in range(4)] + corners[corneri] = str(b) + return ' '.join(corners) + +def corners2csv(bs): + assert len(bs) == 4 + corners = ["None" if b is None else str(b) for b in bs] + return ' '.join(corners) diff --git a/fuzzers/007-timing/timfuz_solve.py b/fuzzers/007-timing/timfuz_solve.py index c9af58b1..dd08c5e0 100644 --- a/fuzzers/007-timing/timfuz_solve.py +++ b/fuzzers/007-timing/timfuz_solve.py @@ -143,4 +143,4 @@ def run(fns_in, corner, run_corner, sub_json=None, sub_csv=None, dedup=True, mas print('Converting to numpy...') names, Anp = A_ds2np(Ads) - run_corner(Anp, np.asarray(b), names, outfn=outfn, verbose=verbose, **kwargs) + run_corner(Anp, np.asarray(b), names, corner, outfn=outfn, verbose=verbose, **kwargs)