prjxray/fuzzers/007-timing/timfuz.py

845 lines
23 KiB
Python

#!/usr/bin/env python
import math
import numpy as np
from collections import OrderedDict
import time
import re
import os
import datetime
import json
import copy
import sys
import random
import glob
from fractions import Fraction
import collections
from benchmark import Benchmark
# prefix to make easier to track
# models do not overlap between PIPs and WIREs
PREFIX_W = 'WIRE'
PREFIX_P = 'PIP'
# extneral site wire (ie site a to b)
PREFIX_SW_EI = 'SITEW-EI'
PREFIX_SW_EO = 'SITEW-EO'
# internal site wire (ie bel a to b within a site)
PREFIX_SW_I = 'SITEW-I'
def sw_ei_vals2s(site_type, src_site_pin, dst_bel, dst_bel_pin):
'''Pack site wire components into a variable string'''
return '%s:%s:%s:%s:%s' % (
PREFIX_SW_EI, site_type, src_site_pin, dst_bel, dst_bel_pin)
def sw_ei_s2vals(s):
prefix, site_type, src_site_pin, dst_bel, dst_bel_pin = s.split(':')
assert prefix == PREFIX_SW_EI
return site_type, src_site_pin, dst_bel, dst_bel_pin
def sw_eo_vals2s(site_type, src_bel, src_bel_pin, dst_site_pin):
return '%s:%s:%s:%s:%s' % (
PREFIX_SW_EO, site_type, src_bel, src_bel_pin, dst_site_pin)
def sw_eo_s2vals(s):
prefix, site_type, src_bel, src_bel_pin, dst_site_pin = s.split(':')
assert prefix == PREFIX_SW_EO
return site_type, src_bel, src_bel_pin, dst_site_pin
def sw_i_vals2s(site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin):
return '%s:%s:%s:%s:%s:%s' % (
PREFIX_SW_I, site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin)
def sw_i_s2vals(s):
prefix, site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin = s.split(
':')
assert prefix == PREFIX_SW_I
return site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin
# Equations are filtered out until nothing is left
class SimplifiedToZero(Exception):
pass
# http://code.activestate.com/recipes/576694/
class OrderedSet(collections.MutableSet):
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
if key not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end]
def discard(self, key):
if key in self.map:
key, prev, next = self.map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def pop(self, last=True):
if not self:
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__, )
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
NAME_ZERO = OrderedSet(
[
"BSW_CLK_ZERO",
"BSW_ZERO",
"B_ZERO",
"C_CLK_ZERO",
"C_DSP_ZERO",
"C_ZERO",
"I_ZERO",
"O_ZERO",
"RC_ZERO",
"R_ZERO",
])
# csv index
corner_s2i = OrderedDict(
[
('fast_max', 0),
('fast_min', 1),
('slow_max', 2),
('slow_min', 3),
])
def allow_zero_eqns():
'''If true, allow a system of equations with no equations'''
return os.getenv('ALLOW_ZERO_EQN', 'N') == 'Y'
def print_eqns(A_ubd, b_ub, verbose=0, lim=3, label=''):
rows = len(b_ub)
print('Sample equations (%s) from %d r' % (label, rows))
prints = 0
#verbose = 1
for rowi, row in enumerate(A_ubd):
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
(not lim or prints < lim)):
line = ' EQN: p%u: ' % rowi
for k, v in sorted(row.items()):
line += '%u*t%d ' % (v, k)
line += '= %d' % b_ub[rowi]
print(line)
prints += 1
def print_name_eqns(A_ubd, b_ub, names, verbose=0, lim=3, label=''):
rows = len(b_ub)
print('Sample equations (%s) from %d r' % (label, rows))
prints = 0
#verbose = 1
for rowi, row in enumerate(A_ubd):
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
(not lim or prints < lim)):
line = ' EQN: p%u: ' % rowi
for k, v in sorted(row.items()):
line += '%u*%s ' % (v, names[k])
line += '= %d' % b_ub[rowi]
print(line)
prints += 1
def print_names(names, verbose=1):
print('Names: %d' % len(names))
for xi, name in enumerate(names):
print(' % 4u % -80s' % (xi, name))
def invb(b_ub):
#return [-b for b in b_ub]
return -np.array(b_ub)
def check_feasible_d(A_ubd, b_ub, names):
A_ub, b_ub_inv = Ab_d2np(A_ubd, b_ub, names)
check_feasible(A_ub, b_ub_inv)
def check_feasible(A_ub, b_ub):
sys.stdout.write('Check feasible ')
sys.stdout.flush()
rows = len(b_ub)
cols = len(A_ub[0])
progress = max(1, rows / 100)
# Chose a high arbitrary value for x
# Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
xs = [10e3 for _i in range(cols)]
# FIXME: use the correct np function to do this for me
# Verify bounds
#b_res = np.matmul(A_ub, xs)
#print(type(A_ub), type(xs)
#A_ub = np.array(A_ub)
#xs = np.array(xs)
#b_res = np.matmul(A_ub, xs)
def my_mul(A_ub, xs):
#print('cols', cols
#print('rows', rows
ret = [None] * rows
for row in range(rows):
this = 0
for col in range(cols):
this += A_ub[row][col] * xs[col]
ret[row] = this
return ret
b_res = my_mul(A_ub, xs)
# Verify bound was respected
for rowi, (this_b, this_b_ub) in enumerate(zip(b_res, b_ub)):
if rowi % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
if this_b >= this_b_ub or this_b > 0:
print(
'% 4d Want res % 10.1f <= % 10.1f <= 0' %
(rowi, this_b, this_b_ub))
raise Exception("Bad ")
print(' done')
def Ab_ub_dt2d(eqns):
'''Convert dict using the rows as keys into a list of dicts + b_ub list (ie return A_ub, b_ub)'''
#return [dict(rowt) for rowt in eqns]
rows = [(OrderedDict(rowt), b) for rowt, b in eqns.items()]
A_ubd, b_ub = zip(*rows)
return list(A_ubd), list(b_ub)
# This significantly reduces runtime
def simplify_rows(Ads, b_ub, remove_zd=False, corner=None):
'''Remove duplicate equations, taking highest delay'''
# dict of constants to highest delay
eqns = OrderedDict()
assert len(Ads) == len(b_ub), (len(Ads), len(b_ub))
assert corner is not None
minmax = {
'fast_max': max,
'fast_min': min,
'slow_max': max,
'slow_min': min,
}[corner]
# An outlier to make unknown values be ignored
T_UNK = {
'fast_max': 0,
'fast_min': 10e9,
'slow_max': 0,
'slow_min': 10e9,
}[corner]
sys.stdout.write('SimpR ')
sys.stdout.flush()
progress = int(max(1, len(b_ub) / 100))
# Equations with a total delay of zero
zero_ds = 0
# Equations with zero elements
# These should have zero delay
zero_es = 0
for loopi, (b, rowd) in enumerate(zip(b_ub, Ads)):
if loopi % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
if remove_zd and not b:
zero_ds += 1
continue
# think ran into these before when taking out ZERO elements
if len(rowd) == 0:
if b != 0:
assert zero_es == 0, 'Unexpected zero element row with non-zero delay'
zero_es += 1
continue
'''
Reduce any constants to canonical form
this simplifies later steps that optimize based on assuming there aren't duplicate constants
Ex:
a = 10
2 a = 30
max corner will get simplified to
a = 15
Otherwise these are not identical equations and would not get simplied
Don't try handling the general case of non-trivial equations
'''
if len(rowd) == 1:
k, v = list(rowd.items())[0]
if v != 1:
rowd = {k: 1}
b = 1.0 * b / v
rowt = Ar_ds2t(rowd)
eqns[rowt] = minmax(eqns.get(rowt, T_UNK), b)
print(' done')
print(
'Simplify rows: %d => %d rows w/ rm zd %d, rm ze %d' %
(len(b_ub), len(eqns), zero_ds, zero_es))
if len(eqns) == 0:
raise SimplifiedToZero()
A_ubd_ret, b_ub_ret = Ab_ub_dt2d(eqns)
#print_eqns(A_ubd_ret, b_ub_ret, verbose=True, label='Debug')
return A_ubd_ret, b_ub_ret
def A_ubr_np2d(row):
'''Convert a single row'''
#d = {}
d = OrderedDict()
for coli, val in enumerate(row):
if val:
d[coli] = val
return d
def A_ub_np2d(A_ub):
'''Convert A_ub entries in numpy matrix to dictionary / sparse form'''
Adi = [None] * len(A_ub)
for i, row in enumerate(A_ub):
Adi[i] = A_ubr_np2d(row)
return Adi
def Ar_di2np(row_di, cols):
rownp = np.zeros(cols)
for coli, val in row_di.items():
# Sign inversion due to way solver works
rownp[coli] = val
return rownp
# NOTE: sign inversion
def A_di2np(Adi, cols):
'''Convert A_ub entries in dictionary / sparse to numpy matrix form'''
return [Ar_di2np(row_di, cols) for row_di in Adi]
def Ar_ds2t(rowd):
'''Convert a dictionary row into a tuple with (column number, value) tuples'''
return tuple(sorted(rowd.items()))
def A_ubr_t2d(rowt):
'''Convert a dictionary row into a tuple with (column number, value) tuples'''
return OrderedDict(rowt)
def A_ub_d2t(A_ubd):
'''Convert rows as dicts to rows as tuples'''
return [Ar_ds2t(rowd) for rowd in A_ubd]
def A_ub_t2d(A_ubd):
'''Convert rows as tuples to rows as dicts'''
return [OrderedDict(rowt) for rowt in A_ubd]
def Ab_d2np(A_ubd, b_ub, names):
A_ub = A_di2np(A_ubd, len(names))
b_ub_inv = invb(b_ub)
return A_ub, b_ub_inv
def Ab_np2d(A_ub, b_ub_inv):
A_ubd = A_ub_np2d(A_ub)
b_ub = invb(b_ub_inv)
return A_ubd, b_ub
def sort_equations(Ads, b):
# Track rows with value column
# Hmm can't sort against np arrays
tosort = [(sorted(row.items()), rowb) for row, rowb in zip(Ads, b)]
#res = sorted(tosort, key=lambda e: e[0])
res = sorted(tosort)
A_ubtr, b_ubr = zip(*res)
return [OrderedDict(rowt) for rowt in A_ubtr], b_ubr
def col_dist(Ads, desc='of', names=[], lim=0):
'''print(frequency distribution of number of elements in a given row'''
rows = len(Ads)
cols = len(names)
fs = {}
for row in Ads:
this_cols = len(row)
fs[this_cols] = fs.get(this_cols, 0) + 1
print(
'Col count distribution (%s) for %dr x %dc w/ %d freqs' %
(desc, rows, cols, len(fs)))
prints = 0
for i, (k, v) in enumerate(sorted(fs.items())):
if lim == 0 or (lim and prints < lim or i == len(fs) - 1):
print(' %d: %d' % (k, v))
prints += 1
if lim and prints == lim:
print(' ...')
def name_dist(A_ubd, desc='of', names=[], lim=0):
'''print(frequency distribution of number of times an element appears'''
rows = len(A_ubd)
cols = len(names)
fs = {i: 0 for i in range(len(names))}
for row in A_ubd:
for k in row.keys():
fs[k] += 1
print('Name count distribution (%s) for %dr x %dc' % (desc, rows, cols))
prints = 0
for namei, name in enumerate(names):
if lim == 0 or (lim and prints < lim or namei == len(fs) - 1):
print(' %s: %d' % (name, fs[namei]))
prints += 1
if lim and prints == lim:
print(' ...')
fs2 = {}
for v in fs.values():
fs2[v] = fs2.get(v, 0) + 1
prints = 0
print('Distribution distribution (%d items)' % len(fs2))
for i, (k, v) in enumerate(sorted(fs2.items())):
if lim == 0 or (lim and prints < lim or i == len(fs2) - 1):
print(' %s: %s' % (k, v))
prints += 1
if lim and prints == lim:
print(' ...')
zeros = fs2.get(0, 0)
if zeros:
raise Exception("%d names without equation" % zeros)
def filter_ncols(A_ubd, b_ub, cols_min=0, cols_max=0):
'''Only keep equations with a few delay elements'''
A_ubd_ret = []
b_ub_ret = []
#print('Removing large rows')
for rowd, b in zip(A_ubd, b_ub):
if (not cols_min or len(rowd) >= cols_min) and (not cols_max or
len(rowd) <= cols_max):
A_ubd_ret.append(rowd)
b_ub_ret.append(b)
print(
'Filter ncols w/ %d <= cols <= %d: %d ==> %d rows' %
(cols_min, cols_max, len(b_ub), len(b_ub_ret)))
assert len(b_ub_ret)
return A_ubd_ret, b_ub_ret
def Ar_di2ds(rowA, names):
row = OrderedDict()
for k, v in rowA.items():
row[names[k]] = v
return row
def A_di2ds(Adi, names):
rows = []
for row_di in Adi:
rows.append(Ar_di2ds(row_di, names))
return rows
def Ar_ds2di(row_ds, names):
def keyi(name):
if name not in names:
names[name] = len(names)
return names[name]
row_di = OrderedDict()
for k, v in row_ds.items():
row_di[keyi(k)] = v
return row_di
def A_ds2di(rows):
names = OrderedDict()
A_ubd = []
for row_ds in rows:
A_ubd.append(Ar_ds2di(row_ds, names))
return list(names.keys()), A_ubd
def A_ds2np(Ads):
names, Adi = A_ds2di(Ads)
return names, A_di2np(Adi, len(names))
def loadc_Ads_mkb(fns, mkb, filt):
bs = []
Ads = []
for fn in fns:
with open(fn, 'r') as f:
# skip header
f.readline()
for l in f:
cols = l.split(',')
ico = bool(int(cols[0]))
corners = cols[1]
vars = cols[2:]
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))
vars = OrderedDict([mkvar(var) for var in vars])
if not filt(ico, corners, vars):
continue
bs.append(mkb(corners))
Ads.append(vars)
return Ads, bs
def loadc_Ads_b(fns, corner, ico=None):
corner = corner or "slow_max"
corneri = corner_s2i[corner]
'''
if ico is not None:
filt = lambda ico_, corners, vars: ico_ == ico
else:
filt = lambda ico_, corners, vars: True
'''
assert ico is None, 'ICO filtering moved to higher levels'
filt = lambda ico_, corners, vars: True
def mkb(val):
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
'''
assert ico is None, 'ICO filtering moved to higher levels'
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
def mkb(val):
return val
return loadc_Ads_mkb(fns, mkb, filt)
def index_names(Ads):
names = OrderedSet()
for row_ds in Ads:
for k1 in row_ds.keys():
names.add(k1)
return names
def load_sub(fn):
j = json.load(open(fn, 'r'))
for name, vals in sorted(j['subs'].items()):
for k, v in vals.items():
vals[k] = Fraction(v[0], v[1])
return j
def row_sub_vars(row, sub_json, strict=False, verbose=False):
if 0 and verbose:
print("")
print(row.items())
delvars = 0
for k in sub_json['zero_names']:
try:
del row[k]
delvars += 1
except KeyError:
pass
if verbose:
print("Deleted %u variables" % delvars)
if verbose:
print('Checking pivots')
print(sorted(row.items()))
for group, pivot in sorted(sub_json['pivots'].items()):
if pivot not in row:
continue
n = row[pivot]
print(' pivot %u %s' % (n, pivot))
#pivots = set(sub_json['pivots'].values()).intersection(row.keys())
for group, pivot in sorted(sub_json['pivots'].items()):
if pivot not in row:
continue
# take the sub out n times
# note constants may be negative
n = row[pivot]
if verbose:
print('pivot %i %s' % (n, pivot))
for subk, subv in sorted(sub_json['subs'][group].items()):
oldn = row.get(subk, type(subv)(0))
rown = -n * subv
rown += oldn
if verbose:
print(" %s: %d => %d" % (subk, oldn, rown))
if rown == 0:
# only becomes zero if didn't previously exist
del row[subk]
if verbose:
print(" del")
else:
row[subk] = rown
row[group] = n
assert pivot not in row
# after all constants are applied, the row should end up positive?
# numeric precision issues previously limited this
# Ex: AssertionError: ('PIP_BSW_2ELSING0', -2.220446049250313e-16)
if strict:
# verify no subs are left
for subs in sub_json['subs'].values():
for sub in subs:
assert sub not in row, 'non-pivot element after group sub %s' % sub
# Verify all constants are positive
for k, v in sorted(row.items()):
assert v > 0, (k, v)
def run_sub_json(Ads, sub_json, strict=False, verbose=False):
'''
strict: complain if a sub doesn't go in evenly
'''
nrows = 0
nsubs = 0
ncols_old = 0
ncols_new = 0
print('Subbing %u rows' % len(Ads))
prints = OrderedSet()
for rowi, row in enumerate(Ads):
if 0 and verbose:
print(row)
if verbose:
print('')
print('Row %u w/ %u elements' % (rowi, len(row)))
row_orig = dict(row)
row_sub_vars(row, sub_json, strict=strict, verbose=verbose)
nrows += 1
if row_orig != row:
nsubs += 1
if verbose:
rowt = Ar_ds2t(row)
if rowt not in prints:
print('row', row)
prints.add(rowt)
ncols_old += len(row_orig)
ncols_new += len(row)
if verbose:
print('')
print("Sub: %u / %u rows changed" % (nsubs, nrows))
print("Sub: %u => %u non-zero row cols" % (ncols_old, ncols_new))
def print_eqns(Ads, b, verbose=0, lim=3, label=''):
rows = len(b)
print('Sample equations (%s) from %d r' % (label, rows))
prints = 0
for rowi, row in enumerate(Ads):
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
(not lim or prints < lim)):
line = ' EQN: p%u: ' % rowi
for k, v in sorted(row.items()):
line += '%u*t%s ' % (v, k)
line += '= %d' % b[rowi]
print(line)
prints += 1
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, bs):
ret = {}
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_bs
return ret
def instances(Ads):
ret = 0
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)
def tilej_stats(tilej):
def tile_stats():
stats = {}
for etype in ('pips', 'wires'):
tm = stats.setdefault(etype, {})
tm['net'] = 0
tm['solved'] = [0, 0, 0, 0]
tm['covered'] = [0, 0, 0, 0]
for tile in tilej['tiles'].values():
for etype in ('pips', 'wires'):
pips = tile[etype]
for k, v in pips.items():
stats[etype]['net'] += 1
for i in range(4):
if pips[k][i]:
stats[etype]['solved'][i] += 1
if pips[k][i] is not None:
stats[etype]['covered'][i] += 1
return stats
def site_stats():
sitesj = tilej['sites']
ret = {
'sites': len(sitesj),
'wires': sum([len(wiresj) for wiresj in sitesj.values()]),
'wires_solved': {},
}
for corneri in corner_s2i.values():
ret['wires_solved'][corneri] = sum(
[
sum(
[
1 if site_wire[corneri] else 0
for site_wire in sitej.values()
])
for sitej in sitesj.values()
])
return ret
tstats = tile_stats()
sstats = site_stats()
for corner, corneri in corner_s2i.items():
print('Corner %s' % corner)
for etype in ('pips', 'wires'):
net = tstats[etype]['net']
solved = tstats[etype]['solved'][corneri]
covered = tstats[etype]['covered'][corneri]
print(
' %s: %u / %u solved, %u / %u covered' %
(etype, solved, net, covered, net))
print(
' sites: %u sites w/ %u solved / %u covered site wires' % (
sstats['sites'], sstats['wires_solved'][corneri],
sstats['wires']))
def load_bounds(bounds_csv, corner):
Ads, b = loadc_Ads_b([bounds_csv], corner)
return Ads2bounds(Ads, b)