Merge pull request #897 from antmicro/clean-007

Fuzzers: 007-timings: remove unused code
This commit is contained in:
Karol Gugala 2019-06-19 21:06:26 +02:00 committed by GitHub
commit 0548c105e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 1 additions and 9444 deletions

View File

@ -1,32 +1,10 @@
# for now hammering on just picorv32
# consider instead aggregating multiple projects
PRJ?=oneblinkw
PRJN?=1
run: all
all: build/timgrid-v.json bel/build/sdf
all: bel/build/sdf
touch run.ok
clean:
rm -rf build
cd speed && $(MAKE) clean
cd timgrid && $(MAKE) clean
cd bel && $(MAKE) clean
cd projects/$(PRJ) && $(MAKE) clean
bel/build/sdf:
cd bel && $(MAKE)
speed/build/speed.json:
cd speed && $(MAKE)
timgrid/build/timgrid.json:
cd timgrid && $(MAKE)
build/timgrid-v.json: projects/$(PRJ)/build/timgrid-v.json
mkdir -p build
cp projects/$(PRJ)/build/timgrid-v.json build/timgrid-v.json
projects/$(PRJ)/build/timgrid-v.json: speed/build/speed.json timgrid/build/timgrid.json
cd projects/$(PRJ) && $(MAKE) N=$(PRJN)

View File

@ -1,275 +0,0 @@
# Timing analysis fuzzer (timfuz)
WIP: 2018-09-10: this process is just starting together and is going to get significant cleanup. But heres the general idea
This runs various designs through Vivado and processes the
resulting timing informationin order to create very simple timing models.
While Vivado might have more involved models (say RC delays, fanout, etc),
timfuz creates simple models that bound realistic min and max element delays.
Currently this document focuses exclusively on fabric timing delays.
## Quick start
```
make -j$(nproc)
```
This will take a relatively long time (say 45 min) and generate build/timgrid-v.json.
You can do a quicker test run (say 3 min) using:
```
make PRJ=oneblinkw PRJN=1 -j$(nproc)
```
## Vivado background
Examples are for a XC750T on Vivado 2017.2.
TODO maybe move to: https://github.com/SymbiFlow/prjxray/wiki/Timing
### Speed index
Vivado seems to associate each delay model with a "speed index".
The fabric has these in two elements: wires (ie one delay element per tile) and pips.
For example, LUT output node A (ex: CLBLL_L_X12Y100/CLBLL_LL_A) has a single wire, also called CLBLL_L_X12Y100/CLBLL_LL_A.
This has speed index 733. Speed models can be queried and we find this corresponds to model C_CLBLL_LL_A.
There are various speed model types:
* bel_delay
* buffer
* buffer_switch
* content_version
* functional
* inpin
* outpin
* parameters
* pass_transistor
* switch
* table_lookup
* tl_buffer
* vt_limits
* wire
IIRC the interconnect is only composed of switch and wire types.
Indices with value 65535 (0xFFFF) never appear. Presumably these are unused models.
They are used for some special models such as those of type "content_version".
For example, the "xilinx" model is of type "content_version".
There are also "cost codes", but these seem to be very course (only around 30 of these)
and are suspected to be related more to PnR than timing model.
### Timing paths
The Vivado timing analyzer can easily output the following:
* Full: delay from BEL pin to BEL pin
* Interconnect only (ICO): delay from BEL pin to BEL pin, but only report interconnect delays (ie exclude site delays)
There is also theoretically an option to report delays up to a specific pip,
but this option is poorly documented and I was unable to get it to work.
Each timing path reports a fast process and a slow process min and max value. So four process values are reported in total:
* fast_max
* fast_min
* slow_max
* slow_min
For example, if the device is end of life, was poorly made, and at an extreme temperature, the delay may be up to the slow_max value.
Since ICO can be reported for each of these, fully analyzing a timing path results in 8 values.
Finally, part of this was analyzing tile regularity to discover what a reasonably compact timing model was.
We verified that all tiles of the same type have exactly the same delay elements.
## Methodology
Make sure you've read the Vivado background section first
### Background
This section briefly describes some of the mathmatics used by this technique that readers may not be familiar with.
These definitions are intended to be good enough to provide a high level understanding and may not be precise.
Numerical analysis: the study of algorithms that use numerical approximation (as opposed to general symbolic manipulations)
numpy: a popular numerical analysis python library. Often written np (import numpy as np).
scipy: provides higher level functionality on top of numpy
sympy ("symbolic python"): like numpy, but is designed to work with rational numbers.
For example, python actually stores 0.1 as 0.1000000000000000055511151231257827021181583404541015625.
However, sympy can represent this as the fraction 1/10, eliminating numerical approximation issues.
Least squares (ex: scipy.optimize.least_squares): approximation method to do a best fit of several variables to a set of equations.
For example, given the equations "x = 1" and "x = 2" there isn't an exact solution.
However, "x = 1.5" is a good compromise since its reasonably solves both equations.
Linear programming (ex: scipy.optimize.linprog aka linprog): approximation method that finds a set of variables that satisfy a set of inequalities.
For example,
Reduced row echelon form (RREF, ex: sympy.Matrix.rref): the simplest form that a system of linear equations can be solved to.
For example, given "x = 1" and "x + y = 9", one can solve for "x = 1" and "y = 8".
However, given "x + y = 1" and "x + y + z = 9", there aren't enough variables to solve this fully.
In this case RREF provides a best effort by giving the ratios between correlated variables.
One variable is normalized to 1 in each of these ratios and is called the "pivot".
Note that if numpy.linalg.solve encounters an unsolvable matrix it may either complain
or generate a false solution due to numerical approximation issues.
### What didn't work
First some quick background on things that didn't work to illustrate why the current approach was chosen.
I first tried to directly through things into linprog, but it unfairly weighted towards arbitrary shared variables. For example, feeding in:
* t0 >= 10
* t0 + t1 >= 100
It would declare "t0 = 100", "t1 = 0" instead of the more intuitive "t0 = 10", "t1 = 90".
I tried to work around this in several ways, notably subtracting equations from each other to produce additional constraints.
This worked okay, but was relatively slow and wasn't approaching nearly solved solutions, even when throwing a lot of data at it.
Next we tried randomly combining a bunch of the equations together and solving them like a regular linear algebra matrix (numpy.linalg.solve).
However, this illustrated that the system was under-constrained.
Further analysis revealed that there are some delay element combinations that simply can't be linearly separated.
This was checked primarily using numpy.linalg.matrix_rank, with some use of numpy.linalg.slogdet.
matrix_rank was preferred over slogdet since its more flexible against non-square matrices.
### Process
Above ultimately led to the idea that we should come up with a set of substitutions that would make the system solvable. This has several advantages:
* Easy to evaluate which variables aren't covered well enough by source data
* Easy to evaluate which variables weren't solved properly (if its fully constrained it should have had a non-zero delay)
At a high level, the above learnings gave this process:
* Find correlated variables by using RREF (sympy.Matrix.rref) to create variable groups
- Note pivots
- You must input a fractional type (ex: fractions.Fraction, but surprisingly not int) to get exact results, otherwise it seems to fall back to numerical approximation
- This is by far the most computationally expensive step
- Mixing RREF substitutions from one data set to another may not be recommended
* Use RREF result to substitute groups on input data, creating new meta variables, but ultimately reducing the number of columns
* Pick a corner
- Examples assume fast_max, but other corners are applicable with appropriate column and sign changes
* De-duplicate by removing equations that are less constrained
- Ex: if solving for a max corner and given:
- t0 + t1 >= 10
- t0 + t1 >= 12
- The first equation is redundant since the second provides a stricter constraint
- This significantly reduces computational time
* Use least squares (scipy.optimize.least_squares) to fit variables near input constraints
- Helps fairly weight delays vs the original input constraints
- Does not guarantee all constraints are met. For example, if this was put in (ignoring these would have been de-duplicated):
- t0 = 10
- t0 = 12
- It may decide something like t0 = 11, which means that the second constraint was not satisfied given we actually want t0 >= 12
* Use linear programming (scipy.optimize.linprog aka linprog) to formally meet all remaining constraints
- Start by filtering out all constraints that are already met. This should eliminate nearly all equations
* Map resulting constraints onto different tile types
- Group delays map onto the group pivot variable, typically setting other elements to 0 (if the processed set is not the one used to create the pivots they may be non-zero)
## TODO, suggestions
Includes
* Consider removing rref
- Intended to understand what can't be solved, maybe not useful in production
* Need more coverage
- Consider instrumenting all fuzzers to output data to feed into timing anlayzer
- Justification: we need a lot of weird cases, we have code that does that in the other fuzzers
* Tune performance parameters
- Can we improve quality of results?
- Do we have a good enough quality checker? (solve_qor.py)
- Compare our vs Xilinx output on random designs
- Does the solve take too long? What could speed it up?
* Investigate min corner
- Tends to solve towards 0, making this not useful
- Low priority: most designs just close timing with setup time
* Investigate characterizing full RC timing model
* Can we split pivot delays among elements instead of entirely into pivot?
* Consider breaking out timing analyzer into its own project / library so it can be re-used on other projects
* Review "--massage". Does this help?
* Review computed site delays vs published Xilinx numbers (DC and AC Switching Characteristics)
* Fabric delay models are RC, but are the site delay models RC as well or maybe just linear?
* Can we create antenna nets to get simpler solves?
* Can we get tcl timing analyzer to analyze a partial route?
- Option says you should be able to do this
- I could not actually get it to work
### Improve test cases
Test cases are somewhat random right now. We could make much more targetted cases using custom routing to improve various fanout estimates and such.
Also there are a lot more elements that are not covered.
At a minimum these should be moved to their own directory.
### ZERO models
Background: there are a number of speed models with the name ZERO in them.
These generally seem to be zero delay, although needs more investigation.
Example: see pseudo pip item below
The timing models will probably significantly improve if these are removed.
In the past I was removing them, but decided to keep them in for now in the spirit of being more conservative.
They include:
* _BSW_CLK_ZERO
* BSW_CLK_ZERO
* _BSW_ZERO
* BSW_ZERO
* _B_ZERO
* B_ZERO
* C_CLK_ZERO
* C_DSP_ZERO
* C_ZERO
* I_ZERO
* _O_ZERO
* O_ZERO
* RC_ZERO
* _R_ZERO
* R_ZERO
### Virtual switchboxes
Background: several low level configuration details are abstracted with virtual configurable elements.
For example, LUT inputs can be rearranged to reduce routing congestion.
However, the LUT configuratioon must be changed to match the switched inputs.
This is handled by the CLBLL_L_INTER switchbox, which doesn't encode any physical configuration bits.
However, this contains PIPs with delay models.
For example, LUT A, input A1 has node CLBLM_M_A1 coming from pip junction CLBLM_M_A1 has PIP CLBLM_IMUX7->CLBLM_M_A1
with speed index 659 (R_ZERO).
This might be further evidence on related issue that ZERO models should probably be removed.
### Incporporate fanout
We could probably significantly improve model granularity by studying delay impact on fanout
### Investigate RC delays
Suspect accuracy could be significantly improved by moving to SPICE based models. But this will take significantly more characterization
### Characterize real hardware
A few people have expressed interest on running tests on real hardware. Will take some thought given we don't have direct access
### Review approximation errors
Ex: one known issue is that the objective function linearly weights small and large delays.
This is only recommended when variables are approximately the same order of magnitude.
For example, carry chain delays are on the order of 7 ps while other delays are 100 ps.
Its very easy to put a large delay on the carry chain while it could have been more appropriately put somewhere else.

View File

@ -1,73 +0,0 @@
'''
pr0ntools
Benchmarking utility
Copyright 2010 John McMaster
'''
import time
def time_str(delta):
fraction = delta % 1
delta -= fraction
delta = int(delta)
seconds = delta % 60
delta /= 60
minutes = delta % 60
delta /= 60
hours = delta
return '%02d:%02d:%02d.%04d' % (hours, minutes, seconds, fraction * 10000)
class Benchmark:
start_time = None
end_time = None
def __init__(self, max_items=None):
# For the lazy
self.start_time = time.time()
self.end_time = None
self.max_items = max_items
self.cur_items = 0
def start(self):
self.start_time = time.time()
self.end_time = None
self.cur_items = 0
def stop(self):
self.end_time = time.time()
def advance(self, n=1):
self.cur_items += n
def set_cur_items(self, n):
self.cur_items = n
def delta_s(self):
if self.end_time:
return self.end_time - self.start_time
else:
return time.time() - self.start_time
def __str__(self):
if self.end_time:
return time_str(self.end_time - self.start_time)
elif self.max_items:
cur_time = time.time()
delta_t = cur_time - self.start_time
rate_s = 'N/A'
if delta_t > 0.000001:
rate = self.cur_items / (delta_t)
rate_s = '%f items / sec' % rate
if rate == 0:
eta_str = 'inf'
else:
remaining = (self.max_items - self.cur_items) / rate
eta_str = time_str(remaining)
else:
eta_str = "indeterminate"
return '%d / %d, ETA: %s @ %s' % (
self.cur_items, self.max_items, eta_str, rate_s)
else:
return time_str(time.time() - self.start_time)

View File

@ -1,134 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json
import numpy as np
import glob
import json
import math
from collections import OrderedDict
from fractions import Fraction
def Adi2matrix_random(A_ubd, b_ub, names):
# random assignment
# was making some empty rows
A_ret = [np.zeros(len(names)) for _i in range(len(names))]
b_ret = np.zeros(len(names))
for row, b in zip(A_ubd, b_ub):
# Randomly assign to a row
dst_rowi = random.randint(0, len(names) - 1)
rownp = Ar_di2np(row, cols=len(names), sf=1)
A_ret[dst_rowi] = np.add(A_ret[dst_rowi], rownp)
b_ret[dst_rowi] += b
return A_ret, b_ret
def Ads2matrix_linear(Ads, b):
names, Adi = A_ds2di(Ads)
cols = len(names)
rows_out = len(b)
A_ret = [np.zeros(cols) for _i in range(rows_out)]
b_ret = np.zeros(rows_out)
dst_rowi = 0
for row_di, row_b in zip(Adi, b):
row_np = Ar_di2np(row_di, cols)
A_ret[dst_rowi] = np.add(A_ret[dst_rowi], row_np)
b_ret[dst_rowi] += row_b
dst_rowi = (dst_rowi + 1) % rows_out
return A_ret, b_ret
def pmatrix(Anp, s):
import sympy
msym = sympy.Matrix(Anp)
print(s)
sympy.pprint(msym)
def pds(Ads, s):
names, Anp = A_ds2np(Ads)
pmatrix(Anp, s)
print('Names: %s' % (names, ))
def run(fns_in, sub_json=None, verbose=False):
assert len(fns_in) > 0
# arbitrary corner...data is thrown away
Ads, b = loadc_Ads_b(fns_in, "slow_max")
if sub_json:
print('Subbing JSON %u rows' % len(Ads))
#pds(Ads, 'Orig')
names_old = index_names(Ads)
run_sub_json(Ads, sub_json, verbose=verbose)
names_new = index_names(Ads)
print("Sub: %u => %u names" % (len(names_old), len(names_new)))
print(names_new)
print('Subbed JSON %u rows' % len(Ads))
names = names_new
#pds(Ads, 'Sub')
else:
names = index_names(Ads)
# Squash into a matrix
# A_ub2, b_ub2 = Adi2matrix_random(A_ubd, b, names)
Amat, _bmat = Ads2matrix_linear(Ads, b)
#pmatrix(Amat, 'Matrix')
'''
The matrix must be fully ranked to even be considered reasonable
Even then, floating point error *possibly* could make it fully ranked, although probably not since we have whole numbers
Hence the slogdet check
'''
print
# https://docs.scipy.org/doc/numpy-dev/reference/generated/numpy.linalg.matrix_rank.html
rank = np.linalg.matrix_rank(Amat)
print('rank: %s / %d col' % (rank, len(names)))
# doesn't work on non-square matrices
if 0:
# https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.linalg.slogdet.html
sign, logdet = np.linalg.slogdet(Amat)
# If the determinant is zero, then sign will be 0 and logdet will be -Inf
if sign == 0 and logdet == float('-inf'):
print('slogdet :( : 0')
else:
print('slogdet :) : %s, %s' % (sign, logdet))
if rank != len(names):
raise Exception(
"Matrix not fully ranked w/ %u / %u" % (rank, len(names)))
def main():
import argparse
parser = argparse.ArgumentParser(
description='Check if sub.json would make a linear equation solvable')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--sub-json', help='')
parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
run(sub_json=sub_json, fns_in=fns_in, verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, simplify_rows, loadc_Ads_b
import glob
def run(fout, fns_in, corner, verbose=0):
Ads, b = loadc_Ads_b(fns_in, corner, ico=True)
Ads, b = simplify_rows(Ads, b, corner=corner)
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
for row_b, row_ds in zip(b, Ads):
# write in same format, but just stick to this corner
out_b = [str(row_b) for _i in range(4)]
ico = '1'
items = [ico, ' '.join(out_b)]
for k, v in sorted(row_ds.items()):
items.append('%u %s' % (v, k))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description='Create a .csv with a single process corner')
parser.add_argument('--verbose', type=int, help='')
parser.add_argument(
'--auto-name',
action='store_true',
help='timing4i.csv => timing4c.csv')
parser.add_argument('--out', default=None, help='Output csv')
parser.add_argument('--corner', help='Output csv')
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args()
bench = Benchmark()
fnout = args.out
if fnout is None:
if args.auto_name:
assert len(args.fns_in) == 1
fnin = args.fns_in[0]
fnout = fnin.replace('timing4i.csv', 'timing4c.csv')
assert fnout != fnin, 'Expect timing4i.csv in'
else:
fnout = '/dev/stdout'
print("Writing to %s" % fnout)
fout = open(fnout, 'w')
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
run(fout=fout, fns_in=fns_in, corner=args.corner, verbose=args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,71 +0,0 @@
#!/usr/bin/env python3
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')
Ads, bs = loadc_Ads_bs([fnin])
print('Sub: %u rows' % len(Ads))
iold = instances(Ads)
names_old = index_names(Ads)
run_sub_json(Ads, sub_json, strict=strict, verbose=verbose)
names = index_names(Ads)
print("Sub: %u => %u names" % (len(names_old), len(names)))
print('Sub: %u => %u instances' % (iold, instances(Ads)))
for row_ds, row_bs in zip(Ads, bs):
yield row_ds, row_bs
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:
for row_ds, row_bs in gen_group(fn_in, sub_json, strict=strict):
row_ico = 1
items = [str(row_ico), ' '.join([str(x) for x in row_bs])]
for k, v in sorted(row_ds.items()):
items.append('%u %s' % (v, k))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description='Substitute .csv to group correlated variables')
parser.add_argument('--verbose', 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')
parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = load_sub(args.sub_json)
try:
run(
fns_in,
args.out,
sub_json=sub_json,
strict=args.strict,
verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,112 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, loadc_Ads_bs, load_sub, Ads2bounds, corners2csv, corner_s2i
def gen_flat(fns_in, sub_json, corner=None):
Ads, bs = loadc_Ads_bs(fns_in)
bounds = Ads2bounds(Ads, bs)
# Elements with zero delay assigned due to sub group
group_zeros = set()
# Elements with a concrete delay
nonzeros = set()
if corner:
zero_row = [None, None, None, None]
zero_row[corner_s2i[corner]] = 0
else:
zero_row = None
for bound_name, bound_bs in bounds.items():
sub = sub_json['subs'].get(bound_name, None)
if bound_name in sub_json['zero_names']:
if zero_row:
yield bound_name, 0
elif sub:
#print('sub', sub)
# put entire delay into pivot
pivot = sub_json['pivots'][bound_name]
assert pivot not in group_zeros
nonzeros.add(pivot)
non_pivot = set(sub.keys() - set([pivot]))
#for name in non_pivot:
# assert name not in nonzeros, (pivot, name, nonzeros)
group_zeros.update(non_pivot)
#print('yield PIVOT', pivot)
yield pivot, bound_bs
else:
nonzeros.add(bound_name)
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 = group_zeros.intersection(nonzeros)
if len(violations):
print('WARNING: %s non-0 non-pivot' % (len(violations)))
# XXX: how to best handle these?
# should they be fixed 0?
if zero_row:
# ZERO names should always be zero
#print('ZEROs: %u' % len(sub_json['zero_names']))
for zero in sub_json['zero_names']:
#print('yield ZERO', zero)
yield zero, zero_row
real_zeros = group_zeros - violations
print(
'Zero candidates: %u w/ %u non-pivot conflicts => %u zeros as solved'
% (len(group_zeros), len(violations), len(real_zeros)))
# Only yield elements not already yielded
for zero in real_zeros:
#print('yield solve-0', zero)
yield zero, zero_row
def run(fns_in, fnout, sub_json, corner=None, verbose=False):
with open(fnout, 'w') as fout:
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
for name, corners in sorted(list(gen_flat(fns_in, sub_json,
corner=corner))):
row_ico = 1
items = [str(row_ico), corners2csv(corners)]
items.append('%u %s' % (1, name))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description='Substitute .csv to ungroup correlated variables')
parser.add_argument('--verbose', 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('--out', default=None, help='output timing delay .csv')
parser.add_argument(
'fns_in',
nargs='+',
help='input timing delay .csv (NOTE: must be single column)')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
sub_json = load_sub(args.sub_json)
try:
run(
args.fns_in,
args.out,
sub_json=sub_json,
verbose=args.verbose,
corner=args.corner)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,29 +0,0 @@
# Demonstrates that cells can have 0 to 2 BEL output pins
# roi/dout_shr_reg[0]: roi/dout_shr[0]_i_1/O roi/dout_shr_reg[0]
set TIME_start [clock clicks -milliseconds]
set site_src_nets 0
set neti 0
set nets [get_nets -hierarchical]
set nnets [llength $nets]
set opins_zero 0
set opins_multi 0
foreach net $nets {
incr neti
puts "Net $neti / $nnets: $net"
set out_pins [get_pins -filter {DIRECTION == OUT} -of_objects $net]
set npins [llength $out_pins]
if {$npins == 0} {
puts " $net zero source pins: $src_pin"
incr opins_zero
}
if {$npins > 1} {
puts " $net multi source pins: $src_pin"
incr opins_multi
}
}
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
puts "Took ms: $TIME_taken"
puts "Result: $opins_zero / $nnets zero"
puts "Result: $opins_multi / $nnets multi"

View File

@ -1,139 +0,0 @@
#!/usr/bin/env python3
import scipy.optimize as optimize
import numpy as np
import glob
import json
import math
import sys
import os
import time
def run_max(Anp, b, verbose=False):
cols = len(Anp[0])
b_ub = -1.0 * b
A_ub = [-1.0 * x for x in Anp]
res = optimize.linprog(
c=[1 for _i in range(cols)],
A_ub=A_ub,
b_ub=b_ub,
bounds=(0, None),
options={
"disp": True,
'maxiter': 1000000,
'bland': True,
'tol': 1e-6,
})
nonzeros = 0
print('Ran')
if res.success:
print('Result sample (%d elements)' % (len(res.x)))
plim = 3
for xi, x in enumerate(res.x):
nonzero = x >= 0.001
if nonzero:
nonzeros += 1
if nonzero and (verbose or (
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
print(' % 4u % 10.1f' % (xi, x))
print('Delay on %d / %d' % (nonzeros, len(res.x)))
def run_min(Anp, b, verbose=False):
cols = len(Anp[0])
b_ub = b
A_ub = Anp
res = optimize.linprog(
c=[-1 for _i in range(cols)],
A_ub=A_ub,
b_ub=b_ub,
bounds=(0, None),
options={
"disp": True,
'maxiter': 1000000,
'bland': True,
'tol': 1e-6,
})
nonzeros = 0
print('Ran')
if res.success:
print('Result sample (%d elements)' % (len(res.x)))
plim = 3
for xi, x in enumerate(res.x):
nonzero = x >= 0.001
if nonzero:
nonzeros += 1
if nonzero and (verbose or (
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
print(' % 4u % 10.1f' % (xi, x))
print('Delay on %d / %d' % (nonzeros, len(res.x)))
def run(verbose=False):
'''
1 * x0 = 10
1 * x0 + 1 * x1 = 100
1 * x0 = 40
2 * x1 = 140
'''
Anp = np.array([
[1, 0],
[1, 1],
[1, 0],
[0, 2],
])
b = np.array([
10,
100,
40,
140,
])
'''
Max
Optimization terminated successfully.
Current function value: 110.000000
Iterations: 4
Ran
Result sample (2 elements)
0 40.0
1 70.0
Delay on 2 / 2
'''
print('Max')
run_max(Anp, b)
print('')
print('')
print('')
'''
Min
Optimization terminated successfully.
Current function value: -80.000000
Iterations: 2
Ran
Result sample (2 elements)
0 10.0
1 70.0
Delay on 2 / 2
'''
print('Min')
run_min(Anp, b)
def main():
import argparse
parser = argparse.ArgumentParser(description='Test')
parser.add_argument('--verbose', action='store_true', help='')
args = parser.parse_args()
run(verbose=args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,22 +0,0 @@
N := 1
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
all: $(SPECIMENS_OK)
$(SPECIMENS_OK):
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
touch $@
run:
$(MAKE) clean
$(MAKE) all
touch run.ok
clean:
rm -rf specimen_[0-9][0-9][0-9]/ seg_clblx.db __pycache__ run.ok
rm -rf vivado*.log vivado_*.str vivado*.jou design *.bits *.dcp *.bit
rm -rf build
.PHONY: all run clean

View File

@ -1,11 +0,0 @@
Characterizes how attributes vary across pips, wires, and nodes. Usage:
```
$ make
$ python3 pip_unique.py
$ python3 wire_unique.py
$ python3 node_unique.py
```
NOTE: "make" will take a long time (about 2.5 hours on my machine)

View File

@ -1,9 +0,0 @@
#!/bin/bash
set -ex
source ${XRAY_GENHEADER}
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
${XRAY_VIVADO} -mode batch -source ../generate.tcl

View File

@ -1,95 +0,0 @@
source ../../../../utils/utils.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../../src/picorv32.v
read_verilog ../top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
proc pips_all {} {
set outdir "."
set fp [open "$outdir/pip_all.txt" w]
set items [get_pips]
puts "Items: [llength $items]"
set needspace 0
set properties [list_property [lindex $items 0]]
foreach item $items {
set needspace 0
foreach property $properties {
set val [get_property $property $item]
if {"$val" ne ""} {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp "$property:$val"
set needspace 1
}
}
puts $fp ""
}
close $fp
}
proc wires_all {} {
set outdir "."
set fp [open "$outdir/wire_all.txt" w]
set items [get_wires]
puts "Items: [llength $items]"
set needspace 0
set properties [list_property [lindex $items 0]]
foreach item $items {
set needspace 0
foreach property $properties {
set val [get_property $property $item]
if {"$val" ne ""} {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp "$property:$val"
set needspace 1
}
}
puts $fp ""
}
close $fp
}
build_design
pips_all
wires_all

View File

@ -1,107 +0,0 @@
#!/usr/bin/env python3
import re
def gen_nodes(fin):
for l in fin:
lj = {}
l = l.strip()
for kvs in l.split():
name, value = kvs.split(':')
'''
NAME:LIOB33_SING_X0Y199/IOB_IBUF0
IS_BAD:0
IS_COMPLETE:1
IS_GND:0 IS_INPUT_PIN:1 IS_OUTPUT_PIN:0 IS_PIN:1 IS_VCC:0
NUM_WIRES:2
PIN_WIRE:1
'''
if name in ('COST_CODE', 'SPEED_CLASS'):
value = int(value)
lj[name] = value
tile_type, xy, wname = re.match(
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
lj['tile_type'] = tile_type
lj['xy'] = xy
lj['wname'] = wname
lj['l'] = l
yield lj
def run(node_fin, verbose=0):
refnodes = {}
nodei = 0
for nodei, anode in enumerate(gen_nodes(node_fin)):
def getk(anode):
return anode['wname']
#return (anode['tile_type'], anode['wname'])
if nodei % 100000 == 0:
print('Check node %d, %u node types' % (nodei, len(refnodes)))
# Existing node?
try:
refnode = refnodes[getk(anode)]
except KeyError:
# Set as reference
refnodes[getk(anode)] = anode
continue
# Verify equivilence
for k in (
'SPEED_CLASS',
'COST_CODE',
'COST_CODE_NAME',
'IS_BAD',
'IS_COMPLETE',
'IS_GND',
'IS_VCC',
):
if k in refnode and k in anode:
def fail():
print('Mismatch on %s' % k)
print(refnode[k], anode[k])
print(refnode['l'])
print(anode['l'])
#assert 0
if k == 'SPEED_CLASS':
# Parameters known to effect SPEED_CLASS
# Verify at least one parameter is different
if refnode[k] != anode[k]:
for k2 in ('IS_PIN', 'IS_INPUT_PIN', 'IS_OUTPUT_PIN',
'PIN_WIRE', 'NUM_WIRES'):
if refnode[k2] != anode[k2]:
break
else:
if 0:
print
fail()
elif refnode[k] != anode[k]:
print
fail()
# A key in one but not the other?
elif k in refnode or k in anode:
assert 0
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description=
'Determines which info is consistent across nodes with the same name')
parser.add_argument('--verbose', type=int, help='')
parser.add_argument(
'node_fn_in',
default='specimen_001/wire_all.txt',
nargs='?',
help='Input file')
args = parser.parse_args()
run(open(args.node_fn_in, 'r'), verbose=args.verbose)

View File

@ -1,143 +0,0 @@
#!/usr/bin/env python3
'''
Triaging tool to help understand where we need more timing coverage
Finds correlated variables to help make better test cases
'''
from timfuz import Benchmark, Ar_di2np, loadc_Ads_b, index_names, A_ds2np, simplify_rows
import numpy as np
import glob
import math
import json
import sympy
from collections import OrderedDict
from fractions import Fraction
import random
from sympy import Rational
def intr(r):
DELTA = 0.0001
for i, x in enumerate(r):
if type(x) is float:
xi = int(x)
assert abs(xi - x) < DELTA
r[i] = xi
def fracr(r):
intr(r)
return [Fraction(x) for x in r]
def fracm(m):
return [fracr(r) for r in m]
def symratr(r):
intr(r)
return [Rational(x) for x in r]
def symratm(m):
return [symratr(r) for r in m]
def intm(m):
[intr(r) for r in m]
return m
def create_matrix(rows, cols):
ret = np.zeros((rows, cols))
for rowi in range(rows):
for coli in range(cols):
ret[rowi][coli] = random.randint(1, 10)
return ret
def create_matrix_sparse(rows, cols):
ret = np.zeros((rows, cols))
for rowi in range(rows):
for coli in range(cols):
if random.randint(0, 5) < 1:
ret[rowi][coli] = random.randint(1, 10)
return ret
def run(
rows=35,
cols=200,
verbose=False,
encoding='np',
sparse=False,
normalize_last=True):
random.seed(0)
if sparse:
mnp = create_matrix_sparse(rows, cols)
else:
mnp = create_matrix(rows, cols)
#print(mnp[0])
if encoding == 'fraction':
msym = sympy.Matrix(fracm(mnp))
elif encoding == 'np':
msym = sympy.Matrix(mnp)
elif encoding == 'sympy':
msym = sympy.Matrix(symratm(mnp))
# this actually produces float results
elif encoding == 'int':
msym = sympy.Matrix(intm(mnp))
else:
assert 0, 'bad encoding: %s' % encoding
print(type(msym[0]), str(msym[0]))
if verbose:
print('names')
print(names)
print('Matrix')
sympy.pprint(msym)
print(
'%s matrix, %u rows x %u cols, sparse: %s, normlast: %s' %
(encoding, len(mnp), len(mnp[0]), sparse, normalize_last))
bench = Benchmark()
try:
rref, pivots = msym.rref(normalize_last=normalize_last)
finally:
print('rref exiting after %s' % bench)
print(type(rref[0]), str(rref[0]))
if verbose:
print('Pivots')
sympy.pprint(pivots)
print('rref')
sympy.pprint(rref)
def main():
import argparse
parser = argparse.ArgumentParser(
description='Matrix solving performance tests')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--sparse', action='store_true', help='')
parser.add_argument('--rows', type=int, help='')
parser.add_argument('--cols', type=int, help='')
parser.add_argument('--normalize-last', type=int, help='')
parser.add_argument('--encoding', default='np', help='')
args = parser.parse_args()
run(
encoding=args.encoding,
rows=args.rows,
cols=args.cols,
sparse=args.sparse,
normalize_last=bool(args.normalize_last),
verbose=args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,91 +0,0 @@
#!/usr/bin/env python3
import re
def gen_wires(fin):
for l in fin:
lj = {}
l = l.strip()
for kvs in l.split():
name, value = kvs.split(':')
lj[name] = value
tile_type, xy, wname = re.match(
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
lj['tile_type'] = tile_type
lj['xy'] = xy
lj['wname'] = wname
lj['l'] = l
yield lj
def run(node_fin, verbose=0):
refnodes = {}
nodei = 0
for nodei, anode in enumerate(gen_wires(node_fin)):
def getk(anode):
return anode['wname']
return (anode['tile_type'], anode['wname'])
if nodei % 100000 == 0:
print('Check node %d, %u node types' % (nodei, len(refnodes)))
# Existing node?
try:
refnode = refnodes[getk(anode)]
except KeyError:
# Set as reference
refnodes[getk(anode)] = anode
continue
k_invariant = (
'CAN_INVERT',
'IS_BUFFERED_2_0',
'IS_BUFFERED_2_1',
'IS_DIRECTIONAL',
'IS_EXCLUDED_PIP',
'IS_FIXED_INVERSION',
'IS_INVERTED',
'IS_PSEUDO',
'IS_SITE_PIP',
'IS_TEST_PIP',
)
k_varies = ('TILE', )
# Verify equivilence
for k in k_invariant:
if k in refnode and k in anode:
def fail():
print('Mismatch on %s' % k)
print(refnode[k], anode[k])
print(refnode['l'])
print(anode['l'])
#assert 0
if refnode[k] != anode[k]:
print('')
fail()
# A key in one but not the other?
elif k in refnode or k in anode:
assert 0
elif k not in k_varies:
assert 0
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description=
'Determines which info is consistent across PIPs with the same name')
parser.add_argument('--verbose', type=int, help='')
parser.add_argument(
'node_fn_in',
default='specimen_001/pip_all.txt',
nargs='?',
help='Input file')
args = parser.parse_args()
run(open(args.node_fn_in, 'r'), verbose=args.verbose)

View File

@ -1,109 +0,0 @@
//move some stuff to minitests/ncy0
`define SEED 32'h12345678
module top(input clk, stb, di, output do);
localparam integer DIN_N = 42;
localparam integer DOUT_N = 79;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi #(.DIN_N(DIN_N), .DOUT_N(DOUT_N))
roi (
.clk(clk),
.din(din),
.dout(dout)
);
endmodule
module roi(input clk, input [DIN_N-1:0] din, output [DOUT_N-1:0] dout);
parameter integer DIN_N = -1;
parameter integer DOUT_N = -1;
/*
//Take out for now to make sure LUTs are more predictable
picorv32 picorv32 (
.clk(clk),
.resetn(din[0]),
.mem_valid(dout[0]),
.mem_instr(dout[1]),
.mem_ready(din[1]),
.mem_addr(dout[33:2]),
.mem_wdata(dout[66:34]),
.mem_wstrb(dout[70:67]),
.mem_rdata(din[33:2])
);
*/
/*
randluts randluts (
.din(din[41:34]),
.dout(dout[78:71])
);
*/
randluts #(.N(150)) randluts (
.din(din[41:34]),
.dout(dout[78:71])
);
endmodule
module randluts(input [7:0] din, output [7:0] dout);
parameter integer N = 250;
function [31:0] xorshift32(input [31:0] xorin);
begin
xorshift32 = xorin;
xorshift32 = xorshift32 ^ (xorshift32 << 13);
xorshift32 = xorshift32 ^ (xorshift32 >> 17);
xorshift32 = xorshift32 ^ (xorshift32 << 5);
end
endfunction
function [63:0] lutinit(input [7:0] a, b);
begin
lutinit[63:32] = xorshift32(xorshift32(xorshift32(xorshift32({a, b} ^ `SEED))));
lutinit[31: 0] = xorshift32(xorshift32(xorshift32(xorshift32({b, a} ^ `SEED))));
end
endfunction
wire [(N+1)*8-1:0] nets;
assign nets[7:0] = din;
assign dout = nets[(N+1)*8-1:N*8];
genvar i, j;
generate
for (i = 0; i < N; i = i+1) begin:is
for (j = 0; j < 8; j = j+1) begin:js
localparam integer k = xorshift32(xorshift32(xorshift32(xorshift32((i << 20) ^ (j << 10) ^ `SEED)))) & 255;
(* KEEP, DONT_TOUCH *)
LUT6 #(
.INIT(lutinit(i, j))
) lut (
.I0(nets[8*i+(k+0)%8]),
.I1(nets[8*i+(k+1)%8]),
.I2(nets[8*i+(k+2)%8]),
.I3(nets[8*i+(k+3)%8]),
.I4(nets[8*i+(k+4)%8]),
.I5(nets[8*i+(k+5)%8]),
.O(nets[8*i+8+j])
);
end
end
endgenerate
endmodule

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
import re
def gen_wires(fin):
for l in fin:
lj = {}
l = l.strip()
for kvs in l.split():
name, value = kvs.split(':')
lj[name] = value
tile_type, xy, wname = re.match(
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
lj['tile_type'] = tile_type
lj['xy'] = xy
lj['wname'] = wname
lj['l'] = l
yield lj
def run(node_fin, verbose=0):
refnodes = {}
nodei = 0
for nodei, anode in enumerate(gen_wires(node_fin)):
def getk(anode):
return anode['wname']
#return (anode['tile_type'], anode['wname'])
if nodei % 100000 == 0:
print('Check node %d, %u node types' % (nodei, len(refnodes)))
# Existing node?
try:
refnode = refnodes[getk(anode)]
except KeyError:
# Set as reference
refnodes[getk(anode)] = anode
continue
k_invariant = (
'COST_CODE',
'IS_INPUT_PIN',
'IS_OUTPUT_PIN',
'IS_PART_OF_BUS',
'NUM_INTERSECTS',
'NUM_TILE_PORTS',
'SPEED_INDEX',
'TILE_PATTERN_OFFSET',
)
k_varies = (
'ID_IN_TILE_TYPE',
'IS_CONNECTED',
'NUM_DOWNHILL_PIPS',
'NUM_PIPS',
'NUM_UPHILL_PIPS',
'TILE_NAME',
)
# Verify equivilence
for k in k_invariant:
if k in refnode and k in anode:
def fail():
print('Mismatch on %s' % k)
print(refnode[k], anode[k])
print(refnode['l'])
print(anode['l'])
#assert 0
if refnode[k] != anode[k]:
print
fail()
# A key in one but not the other?
elif k in refnode or k in anode:
assert 0
elif k not in k_varies:
assert 0
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description=
'Determines which info is consistent across wires with the same name')
parser.add_argument('--verbose', type=int, help='')
parser.add_argument(
'node_fn_in',
default='specimen_001/wire_all.txt',
nargs='?',
help='Input file')
args = parser.parse_args()
run(open(args.node_fn_in, 'r'), verbose=args.verbose)

View File

@ -1 +0,0 @@
Collect info on ZERO speed classes

View File

@ -1,76 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import time
import json
def run_types(tilej, verbose=False):
def process(etype):
# dict[model] = set((tile, wire/pip))
zeros = {}
print('Processing %s' % etype)
# Index delay models by type, recording where they occured
for tilek, tilev in tilej['tiles'].items():
for ename, emodel in tilev[etype].items():
if emodel.find('ZERO') >= 0:
zeros.setdefault(emodel, set()).add((tilek, ename))
# Print out delay model instances
print('%s ZERO types: %u, %s' % (etype, len(zeros), zeros.keys()))
print(
'%s ZERO instances: %u' %
(etype, sum([len(x) for x in zeros.values()])))
for model in sorted(zeros.keys()):
modelv = zeros[model]
print('Model: %s' % model)
for tile_name, element_name in sorted(list(modelv)):
print(' %s: %s' % (tile_name, element_name))
process('wires')
print('')
process('pips')
def run_prefix(tilej, verbose=False):
def process(etype):
prefixes = set()
print('Processing %s' % etype)
# Index delay models by type, recording where they occured
for tilek, tilev in tilej['tiles'].items():
for ename, emodel in tilev[etype].items():
prefix = emodel.split('_')[0]
prefixes.add(prefix)
print('%s prefixes: %u' % (etype, len(prefixes)))
for prefix in sorted(prefixes):
print(' %s' % prefix)
process('wires')
print('')
process('pips')
def run(fnin, verbose=False):
tilej = json.load((open(fnin, 'r')))
run_types(tilej)
print('')
print('')
run_prefix(tilej)
def main():
import argparse
parser = argparse.ArgumentParser(description='Solve timing solution')
parser.add_argument(
'fnin',
default="../timgrid/build/timgrid-s.json",
nargs='?',
help='input timgrid JSON')
args = parser.parse_args()
run(args.fnin, verbose=False)
if __name__ == '__main__':
main()

View File

@ -1,3 +0,0 @@
specimen_*
build

View File

@ -1,4 +0,0 @@
all:
echo "FIXME: tie projects together"
false

View File

@ -1,57 +0,0 @@
# Run corner specific calculations
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
CORNER=slow_max
ALLOW_ZERO_EQN?=N
BADPRJ_OK?=N
BUILD_DIR?=build/MUST_SET
CSV_BASENAME=timing4i.csv
all: $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/qor.txt
run:
$(MAKE) clean
$(MAKE) all
touch run.ok
clean:
rm -rf $(BUILD_DIR)/$(CORNER)
.PHONY: all run clean
$(BUILD_DIR)/$(CORNER):
mkdir -p $(BUILD_DIR)/$(CORNER)
# parent should have built this
$(BUILD_DIR)/checksub:
false
$(BUILD_DIR)/$(CORNER)/leastsq.csv: $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv $(BUILD_DIR)/checksub $(BUILD_DIR)/$(CORNER)
# Create a rough timing model that approximately fits the given paths
python3 $(TIMFUZ_DIR)/solve_leastsq.py $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/leastsq.csv.tmp
mv $(BUILD_DIR)/$(CORNER)/leastsq.csv.tmp $(BUILD_DIR)/$(CORNER)/leastsq.csv
$(BUILD_DIR)/$(CORNER)/linprog.csv: $(BUILD_DIR)/$(CORNER)/leastsq.csv $(BUILD_DIR)/grouped.csv
# Tweak rough timing model, making sure all constraints are satisfied
ALLOW_ZERO_EQN=$(ALLOW_ZERO_EQN) python3 $(TIMFUZ_DIR)/solve_linprog.py --bounds-csv $(BUILD_DIR)/$(CORNER)/leastsq.csv --massage $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp
mv $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp $(BUILD_DIR)/$(CORNER)/linprog.csv
$(BUILD_DIR)/$(CORNER)/flat.csv: $(BUILD_DIR)/$(CORNER)/linprog.csv
# Take separated variables and back-annotate them to the original timing variables
python3 $(TIMFUZ_DIR)/csv_group2flat.py --sub-json $(BUILD_DIR)/sub.json --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/flat.csv.tmp $(BUILD_DIR)/$(CORNER)/linprog.csv
mv $(BUILD_DIR)/$(CORNER)/flat.csv.tmp $(BUILD_DIR)/$(CORNER)/flat.csv
$(BUILD_DIR)/$(CORNER)/timgrid-vc.json: $(BUILD_DIR)/$(CORNER)/flat.csv
# Final processing
# Insert timing delays into actual tile layouts
python3 $(TIMFUZ_DIR)/tile_annotate.py --timgrid-s $(TIMFUZ_DIR)/timgrid/build/timgrid-s.json --out $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/flat.csv
$(BUILD_DIR)/$(CORNER)/qor.txt: $(BUILD_DIR)/$(CORNER)/flat.csv
ifeq ($(SOLVING),i)
python3 $(TIMFUZ_DIR)/solve_qor.py --corner $(CORNER) --bounds-csv $(BUILD_DIR)/$(CORNER)/flat.csv specimen_*/timing4i.csv >$(BUILD_DIR)/$(CORNER)/qor.txt.tmp
mv $(BUILD_DIR)/$(CORNER)/qor.txt.tmp $(BUILD_DIR)/$(CORNER)/qor.txt
else
# FIXME
touch $(BUILD_DIR)/$(CORNER)/qor.txt
endif

View File

@ -1,16 +0,0 @@
#!/bin/bash
source ${XRAY_GENHEADER}
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
timing_txt2csv () {
python3 $TIMFUZ_DIR/timing_txt2icsv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4i.csv.tmp timing4.txt
mv timing4i.csv.tmp timing4i.csv
python3 $TIMFUZ_DIR/timing_txt2scsv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4s.csv.tmp timing4.txt
mv timing4s.csv.tmp timing4s.csv
# delete really large file, see https://github.com/SymbiFlow/prjxray/issues/137
rm timing4.txt
}

View File

@ -1,74 +0,0 @@
# Interconnect and site (IS) high level aggregation
# Creates corner data and aggregates them together
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
SOLVING=i
CSV_BASENAME=timing4$(SOLVING).csv
BUILD_DIR?=build/MUST_SET
SPECIMENS :=
CSVS := $(addsuffix /$(CSV_BASENAME),$(SPECIMENS))
RREF_CORNER=slow_max
# Set ZERO elements to zero delay (as is expected they should be)
RMZERO?=N
RREF_ARGS=
ifeq ($(RMZERO),Y)
RREF_ARGS+=--rm-zero
endif
# FIXME: clean this up by generating targets from CORNERS
# fast_max => build/i/fast_max/timgrid-vc.json
TIMGRID_VCS=$(BUILD_DIR)/fast_max/timgrid-vc.json $(BUILD_DIR)/fast_min/timgrid-vc.json $(BUILD_DIR)/slow_max/timgrid-vc.json $(BUILD_DIR)/slow_min/timgrid-vc.json
#TIMGRID_VCS=$(addsuffix /timgrid-vc.json,$(addprefix $(BUILD_DIR_I)/,$(CORNERS)))
# make $(BUILD_DIR)/checksub first
$(BUILD_DIR)/fast_max/timgrid-vc.json: $(BUILD_DIR)/checksub
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_max
$(BUILD_DIR)/fast_min/timgrid-vc.json: $(BUILD_DIR)/checksub
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_min
$(BUILD_DIR)/slow_max/timgrid-vc.json: $(BUILD_DIR)/checksub
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_max
$(BUILD_DIR)/slow_min/timgrid-vc.json: $(BUILD_DIR)/checksub
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_min
# Normally require all projects to complete
# If BADPRJ_OK is allowed, only take projects that were successful
# FIXME: couldn't get call to work
exist_csvs = \
for f in $(CSVS); do \
if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then \
echo $$f; \
fi; \
done
all: $(BUILD_DIR)/timgrid-v.json
# rref should be the same regardless of corner
$(BUILD_DIR)/sub.json: $(SPECIMENS_OK)
mkdir -p $(BUILD_DIR)
# Discover which variables can be separated
# This is typically the longest running operation
\
csvs=$$(for f in $(CSVS); do if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then echo $$f; fi; done) ; \
echo $$csvs ; \
python3 $(TIMFUZ_DIR)/rref.py --corner $(RREF_CORNER) --simplify $(RREF_ARGS) --out $(BUILD_DIR)/sub.json.tmp $$csvs
mv $(BUILD_DIR)/sub.json.tmp $(BUILD_DIR)/sub.json
$(BUILD_DIR)/grouped.csv: $(SPECIMENS_OK) $(BUILD_DIR)/sub.json
# Separate variables
\
csvs=$$(for f in $(CSVS); do if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then echo $$f; fi; done) ; \
python3 $(TIMFUZ_DIR)/csv_flat2group.py --sub-json $(BUILD_DIR)/sub.json --strict --out $(BUILD_DIR)/grouped.csv.tmp $$csvs
mv $(BUILD_DIR)/grouped.csv.tmp $(BUILD_DIR)/grouped.csv
$(BUILD_DIR)/checksub: $(BUILD_DIR)/grouped.csv $(BUILD_DIR)/sub.json
# Verify sub.json makes a cleanly solvable solution with no non-pivot leftover
python3 $(TIMFUZ_DIR)/checksub.py --sub-json $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv
touch $(BUILD_DIR)/checksub
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_VCS)
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_VCS)

View File

@ -1,4 +0,0 @@
# so simple that likely no linprog adjustments will be needed
ALLOW_ZERO_EQN?=Y
include ../project.mk

View File

@ -1,2 +0,0 @@
Minimal, simple test

View File

@ -1,8 +0,0 @@
#!/bin/bash
set -ex
source ../generate.sh
${XRAY_VIVADO} -mode batch -source ../generate.tcl
timing_txt2csv

View File

@ -1,45 +0,0 @@
source ../../../../../utils/utils.tcl
source ../../project.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
#randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
build_design
write_info4

View File

@ -1,37 +0,0 @@
module roi (
input wire clk,
output wire out);
reg [23:0] counter;
assign out = counter[23] ^ counter[22] ^ counter[2] && counter[1] || counter[0];
always @(posedge clk) begin
counter <= counter + 1;
end
endmodule
module top(input wire clk, input wire stb, input wire di, output wire do);
localparam integer DIN_N = 0;
localparam integer DOUT_N = 1;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi(
.clk(clk),
.out(dout[0])
);
endmodule

View File

@ -1,2 +0,0 @@
include ../project.mk

View File

@ -1,3 +0,0 @@
picorv32 uses more advanced structures than simple LUT/FF tests (such as carry chain)
It attempts to provide some realistic timing data wrt fanout and used fabric

View File

@ -1,8 +0,0 @@
#!/bin/bash
set -ex
source ../generate.sh
${XRAY_VIVADO} -mode batch -source ../generate.tcl
timing_txt2csv

View File

@ -1,46 +0,0 @@
source ../../../../../utils/utils.tcl
source ../../project.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../../../src/picorv32.v
read_verilog ../top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
build_design
write_info4

View File

@ -1,109 +0,0 @@
//move some stuff to minitests/ncy0
`define SEED 32'h12345678
module top(input clk, stb, di, output do);
localparam integer DIN_N = 42;
localparam integer DOUT_N = 79;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi #(.DIN_N(DIN_N), .DOUT_N(DOUT_N))
roi (
.clk(clk),
.din(din),
.dout(dout)
);
endmodule
module roi(input clk, input [DIN_N-1:0] din, output [DOUT_N-1:0] dout);
parameter integer DIN_N = -1;
parameter integer DOUT_N = -1;
/*
//Take out for now to make sure LUTs are more predictable
picorv32 picorv32 (
.clk(clk),
.resetn(din[0]),
.mem_valid(dout[0]),
.mem_instr(dout[1]),
.mem_ready(din[1]),
.mem_addr(dout[33:2]),
.mem_wdata(dout[66:34]),
.mem_wstrb(dout[70:67]),
.mem_rdata(din[33:2])
);
*/
/*
randluts randluts (
.din(din[41:34]),
.dout(dout[78:71])
);
*/
randluts #(.N(150)) randluts (
.din(din[41:34]),
.dout(dout[78:71])
);
endmodule
module randluts(input [7:0] din, output [7:0] dout);
parameter integer N = 250;
function [31:0] xorshift32(input [31:0] xorin);
begin
xorshift32 = xorin;
xorshift32 = xorshift32 ^ (xorshift32 << 13);
xorshift32 = xorshift32 ^ (xorshift32 >> 17);
xorshift32 = xorshift32 ^ (xorshift32 << 5);
end
endfunction
function [63:0] lutinit(input [7:0] a, b);
begin
lutinit[63:32] = xorshift32(xorshift32(xorshift32(xorshift32({a, b} ^ `SEED))));
lutinit[31: 0] = xorshift32(xorshift32(xorshift32(xorshift32({b, a} ^ `SEED))));
end
endfunction
wire [(N+1)*8-1:0] nets;
assign nets[7:0] = din;
assign dout = nets[(N+1)*8-1:N*8];
genvar i, j;
generate
for (i = 0; i < N; i = i+1) begin:is
for (j = 0; j < 8; j = j+1) begin:js
localparam integer k = xorshift32(xorshift32(xorshift32(xorshift32((i << 20) ^ (j << 10) ^ `SEED)))) & 255;
(* KEEP, DONT_TOUCH *)
LUT6 #(
.INIT(lutinit(i, j))
) lut (
.I0(nets[8*i+(k+0)%8]),
.I1(nets[8*i+(k+1)%8]),
.I2(nets[8*i+(k+2)%8]),
.I3(nets[8*i+(k+3)%8]),
.I4(nets[8*i+(k+4)%8]),
.I5(nets[8*i+(k+5)%8]),
.O(nets[8*i+8+j])
);
end
end
endgenerate
endmodule

View File

@ -1,2 +0,0 @@
include ../project.mk

View File

@ -1,2 +0,0 @@
LUTs are physically laid out in an array and directly connected to a test pattern generator

View File

@ -1,92 +0,0 @@
#!/usr/bin/env python
import argparse
parser = argparse.ArgumentParser(description='')
parser.add_argument('--sdx', default='8', help='')
parser.add_argument('--sdy', default='4', help='')
args = parser.parse_args()
'''
Generate in pairs
Fill up switchbox quad for now
Create random connections between the LUTs
See how much routing pressure we can generate
Start with non-random connections to the LFSR for solver comparison
Start at SLICE_X16Y102
'''
SBASE = (16, 102)
SDX = int(args.sdx, 0)
SDY = int(args.sdy, 0)
nlut = 4 * SDX * SDY
nin = 6 * nlut
nout = nlut
print('//placelut simple')
print('//SBASE: %s' % (SBASE, ))
print('//SDX: %s' % (SDX, ))
print('//SDY: %s' % (SDX, ))
print('//nlut: %s' % (nlut, ))
print(
'''\
module roi (
input wire clk,
input wire [%u:0] ins,
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
ini = 0
outi = 0
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
for luty in xrange(SBASE[1], SBASE[1] + SDY):
loc = "SLICE_X%uY%u" % (lutx, luty)
for belc in 'ABCD':
bel = '%c6LUT' % belc
print(
'''\
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
LUT6 #(
.INIT(64'hBAD1DEA_1DEADCE0)
) %s (''') % (loc, bel, 'lut_x%uy%u_%c' % (lutx, luty, belc))
for i in xrange(6):
print('''\
.I%u(ins[%u]),''' % (i, ini))
ini += 1
print('''\
.O(outs[%u]));''') % (outi, )
outi += 1
assert nin == ini
assert nout == outi
print(
'''
endmodule
module top(input wire clk, input wire stb, input wire di, output wire do);
localparam integer DIN_N = %u;
localparam integer DOUT_N = %u;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi(
.clk(clk),
.ins(din),
.outs(dout)
);
endmodule''') % (nin, nout)

View File

@ -1,11 +0,0 @@
#!/bin/bash
set -ex
source ${XRAY_GENHEADER}
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
python3 ../generate.py --sdx 4 --sdy 4 >top.v
${XRAY_VIVADO} -mode batch -source ../generate.tcl
python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4.csv timing4.txt

View File

@ -1,45 +0,0 @@
source ../../../../../utils/utils.tcl
source ../../project.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
build_design
write_info4

View File

@ -1,2 +0,0 @@
include ../project.mk

View File

@ -1,2 +0,0 @@
LUTs are physically laid out in an array and connected to test pattern generator and fed back to other LUTs

View File

@ -1,103 +0,0 @@
#!/usr/bin/env python
'''
Note: vivado will (by default) fail bitgen DRC on LUT feedback loops
Looks like can probably be disabled, but we actually don't need a bitstream for timing analysis
'''
import argparse
import random
random.seed()
parser = argparse.ArgumentParser(description='')
parser.add_argument('--sdx', default='8', help='')
parser.add_argument('--sdy', default='4', help='')
args = parser.parse_args()
'''
Generate in pairs
Fill up switchbox quad for now
Create random connections between the LUTs
See how much routing pressure we can generate
Start with non-random connections to the LFSR for solver comparison
Start at SLICE_X16Y102
'''
SBASE = (16, 102)
SDX = int(args.sdx, 0)
SDY = int(args.sdy, 0)
nlut = 4 * SDX * SDY
nin = 6 * nlut
nout = nlut
print('//placelut w/ feedback')
print('//SBASE: %s' % (SBASE, ))
print('//SDX: %s' % (SDX, ))
print('//SDY: %s' % (SDX, ))
print('//nlut: %s' % (nlut, ))
print(
'''\
module roi (
input wire clk,
input wire [%u:0] ins,
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
ini = 0
outi = 0
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
for luty in xrange(SBASE[1], SBASE[1] + SDY):
loc = "SLICE_X%uY%u" % (lutx, luty)
for belc in 'ABCD':
bel = '%c6LUT' % belc
print(
'''\
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
LUT6 #(
.INIT(64'hBAD1DEA_1DEADCE0)
) %s (''') % (loc, bel, 'lut_x%uy%u_%c' % (lutx, luty, belc))
for i in xrange(6):
if random.randint(0, 9) < 1:
wfrom = 'ins[%u]' % ini
ini += 1
else:
wfrom = 'outs[%u]' % random.randint(0, nout - 1)
print('''\
.I%u(%s),''' % (i, wfrom))
print('''\
.O(outs[%u]));''') % (outi, )
outi += 1
#assert nin == ini
assert nout == outi
print(
'''
endmodule
module top(input wire clk, input wire stb, input wire di, output wire do);
localparam integer DIN_N = %u;
localparam integer DOUT_N = %u;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi(
.clk(clk),
.ins(din),
.outs(dout)
);
endmodule''') % (nin, nout)

View File

@ -1,9 +0,0 @@
#!/bin/bash
set -ex
source ../generate.sh
python3 ../generate.py --sdx 4 --sdy 4 >top.v
${XRAY_VIVADO} -mode batch -source ../generate.tcl
timing_txt2csv

View File

@ -1,45 +0,0 @@
source ../../../../../utils/utils.tcl
source ../../project.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
build_design
write_info4

View File

@ -1,2 +0,0 @@
include ../project.mk

View File

@ -1,3 +0,0 @@
LUTs are physically laid out in an array and connected to test pattern generator and fed back to other LUTs.
FFs are randomly inserted between connections.

View File

@ -1,127 +0,0 @@
#!/usr/bin/env python
'''
Note: vivado will (by default) fail bitgen DRC on LUT feedback loops
Looks like can probably be disabled, but we actually don't need a bitstream for timing analysis
ERROR: [Vivado 12-2285] Cannot set LOC property of instance 'roi/lut_x22y102_D', Instance roi/lut_x22y102_D can not be placed in D6LUT of site SLICE_X18Y103 because the bel is occupied by roi/lut_x18y103_D(port:). This could be caused by bel constraint conflict
Resolution: When using BEL constraints, ensure the BEL constraints are defined before the LOC constraints to avoid conflicts at a given site.
'''
import argparse
import random
random.seed()
parser = argparse.ArgumentParser(description='')
parser.add_argument('--sdx', default='8', help='')
parser.add_argument('--sdy', default='4', help='')
args = parser.parse_args()
'''
Generate in pairs
Fill up switchbox quad for now
Create random connections between the LUTs
See how much routing pressure we can generate
Start with non-random connections to the LFSR for solver comparison
Start at SLICE_X16Y102
'''
SBASE = (16, 102)
SDX = int(args.sdx, 0)
SDY = int(args.sdy, 0)
nlut = 4 * SDX * SDY
nin = 6 * nlut
nout = nlut
print('//placelut w/ FF + feedback')
print('//SBASE: %s' % (SBASE, ))
print('//SDX: %s' % (SDX, ))
print('//SDY: %s' % (SDX, ))
print('//nlut: %s' % (nlut, ))
print(
'''\
module roi (
input wire clk,
input wire [%u:0] ins,
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
ini = 0
outi = 0
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
for luty in xrange(SBASE[1], SBASE[1] + SDY):
loc = "SLICE_X%uY%u" % (lutx, luty)
for belc in 'ABCD':
bel = '%c6LUT' % belc
name = 'lut_x%uy%u_%c' % (lutx, luty, belc)
print(
'''\
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
LUT6 #(
.INIT(64'hBAD1DEA_1DEADCE0)
) %s (''') % (loc, bel, name)
for i in xrange(6):
rval = random.randint(0, 9)
if rval < 3:
wfrom = 'ins[%u]' % ini
ini += 1
#elif rval < 6:
# wfrom = 'outsr[%u]' % random.randint(0, nout - 1)
else:
wfrom = 'outs[%u]' % random.randint(0, nout - 1)
print('''\
.I%u(%s),''' % (i, wfrom))
out_w = name + '_o'
print('''\
.O(%s));''') % (out_w, )
outs_w = "outs[%u]" % outi
if random.randint(0, 9) < 5:
print(' assign %s = %s;' % (outs_w, out_w))
else:
out_r = name + '_or'
print(
'''\
reg %s;
assign %s = %s;
always @(posedge clk) begin
%s = %s;
end
''' % (out_r, outs_w, out_r, out_r, out_w))
outi += 1
#assert nin == ini
assert nout == outi
print(
'''
endmodule
module top(input wire clk, input wire stb, input wire di, output wire do);
localparam integer DIN_N = %u;
localparam integer DOUT_N = %u;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi(
.clk(clk),
.ins(din),
.outs(dout)
);
endmodule''') % (nin, nout)

View File

@ -1,9 +0,0 @@
#!/bin/bash
set -ex
source ../generate.sh
python3 ../generate.py --sdx 4 --sdy 4 >top.v
${XRAY_VIVADO} -mode batch -source ../generate.tcl
timing_txt2csv

View File

@ -1,45 +0,0 @@
source ../../../../../utils/utils.tcl
source ../../project.tcl
proc build_design {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog top.v
synth_design -top top
puts "Locking pins"
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
puts "Package stuff"
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
puts "pblocking"
create_pblock roi
set roipb [get_pblocks roi]
add_cells_to_pblock $roipb [get_cells roi]
resize_pblock $roipb -add "$::env(XRAY_ROI)"
puts "randplace"
randplace_pblock 50 roi
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
puts "dedicated route"
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
# disable combinitorial loop
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
#write_bitstream -force design.bit
}
build_design
write_info4

View File

@ -1,55 +0,0 @@
# project.mk: build specimens (run vivado), compute rref
# corner.mk: run corner specific calculations
N := 1
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
# Allow an empty system of equations?
# for testing only on small projects
ALLOW_ZERO_EQN?=N
# Constrained projects may fail to build
# Set to Y to make a best effort to suck in the ones that did build
BADPRJ_OK?=N
BUILD_DIR?=build
# interconnect
BUILD_DIR_I?=$(BUILD_DIR)/i
# site
BUILD_DIR_S?=$(BUILD_DIR)/s
CORNERS=fast_max fast_min slow_max slow_min
TIMGRID_V_I=$(BUILD_DIR_I)/timgrid-v.json
TIMGRID_V_S=$(BUILD_DIR_S)/timgrid-v.json
all: $(BUILD_DIR)/timgrid-v.json
$(SPECIMENS_OK):
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
touch $@
run:
$(MAKE) clean
$(MAKE) all
touch run.ok
clean:
rm -rf specimen_[0-9][0-9][0-9]/ seg_clblx.segbits __pycache__ run.ok
rm -rf vivado*.log vivado_*.str vivado*.jou design *.bits *.dcp *.bit
rm -rf $(BUILD_DIR)
.PHONY: all run clean
$(TIMGRID_V_I): $(SPECIMENS_OK)
$(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_I) SOLVING=i SPECIMENS=$(SPECIMENS) all
i: $(TIMGRID_V_I)
$(TIMGRID_V_S): $(SPECIMENS_OK)
$(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_S) SOLVING=s SPECIMENS=$(SPECIMENS) all
s: $(TIMGRID_V_S)
.PHONY: i s
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_V_I) $(TIMGRID_V_S)
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_V_I) $(TIMGRID_V_S)

View File

@ -1,221 +0,0 @@
# General notes:
# Observed nets with 1 to 2 source cell pins
# 2 example
# [get_pins -filter {DIRECTION == OUT} -of_objects $net]
# roi/dout_shr[0]_i_1/O: BEL, visible
# roi/dout_shr_reg[0]: no BEL, not visible, named after the net
# I don't understand what the relationship between these two is
# Maybe register retiming?
# Observed nets with 0 to 1 source bel pins
# 0 example: <const0>
# Recommendation: assume there is at most one source BEL
# Take that as the source BEL if it exists, otherwise don't do any source analysis
# get_timing_paths vs get_net_delays
# get_timing_paths is built on top of get_net_delays?
# has some different info, but is fixed on the slow max corner
# *possibly* reports slightly better info within a site, but would need to verify that
# Things safe to assume:
# -Each net has at least one delay?
# -Cell exists for every delay path
# -Each net has at least one destination BEL pin
# Things not safe to assume
# -Each net has at least one source BEL pin (ex: <const0>)
proc list_format {l delim} {
set ret ""
set needspace 0
foreach x $l {
if $needspace {
set ret "$ret$delim$x"
} else {
set ret "$x"
}
set needspace 1
}
return $ret
}
proc line_net_external {fp net src_site src_site_type src_site_pin src_bel src_bel_pin dst_site dst_site_type dst_site_pin dst_bel dst_bel_pin ico fast_max fast_min slow_max slow_min pips_out wires_out} {
puts $fp "NET,$net,$src_site,$src_site_type,$src_site_pin,$src_bel,$src_bel_pin,$dst_site,$dst_site_type,$dst_site_pin,$dst_bel,$dst_bel_pin,$ico,$fast_max,$fast_min,$slow_max,$slow_min,$pips_out,$wires_out"
}
proc line_net_internal {fp net site site_type src_bel src_bel_pin dst_bel dst_bel_pin ico fast_max fast_min slow_max slow_min} {
set src_site $site
set src_site_type $site_type
set src_site_pin ""
set dst_site $site
set dst_site_type $site_type
set dst_site_pin ""
set pips_out ""
set wires_out ""
puts $fp "NET,$net,$src_site,$src_site_type,$src_site_pin,$src_bel,$src_bel_pin,$dst_site,$dst_site_type,$dst_site_pin,$dst_bel,$dst_bel_pin,$ico,$fast_max,$fast_min,$slow_max,$slow_min,$pips_out,$wires_out"
}
proc write_info4 {} {
set outdir "."
set fp [open "$outdir/timing4.txt" w]
# bel as site/bel, so don't bother with site
puts $fp "linetype,net,src_site,src_site_type,src_site_pin,src_bel,src_bel_pin,dst_site,dst_site_type,dst_site_pin,dst_bel,dst_bel_pin,ico,fast_max,fast_min,slow_max,slow_min,pips,wires"
set TIME_start [clock clicks -milliseconds]
set delays_net 0
set nets_no_src_cell 0
set nets_no_src_bel 0
set lines_no_int 0
set lines_some_int 0
set neti 0
set nets [get_nets -hierarchical]
#set nets [get_nets clk]
#set nets [get_nets roi/counter[0]_i_2_n_0]
set nnets [llength $nets]
foreach net $nets {
incr neti
puts "Net $neti / $nnets: $net"
# there are some special places like on IOB where you may not have a cell source pin
# this is due to special treatment where BEL vs SITE are blurred
# The semantics of get_pins -leaf is kind of odd
# When no passthrough LUTs exist, it has no effect
# When passthrough LUT present:
# -w/o -leaf: some pins + passthrough LUT pins
# -w/ -leaf: different pins + passthrough LUT pins
# With OUT filter this seems to be sufficient
set src_cell_pins [get_pins -leaf -filter {DIRECTION == OUT} -of_objects $net]
if {$src_cell_pins eq ""} {
incr nets_no_src_cell
# ex: IOB internal bel net
puts " SKIP: no source cell"
continue
}
# 0 to 2 of these
# Seems when 2 of them they basically have the same bel + cell
# Make a best effort and move forward
set src_cell_pin [lindex $src_cell_pins 0]
set src_cell [get_cells -of_objects $src_cell_pin]
# Only applicable if in a site
set src_bel [get_bels -of_objects $src_cell]
if {$src_bel eq ""} {
# just throw out cases where source site doesn't exist
# these are very special, don't really have timing info anyway
# rework these later if they become problematic
incr nets_no_src_bel
puts " SKIP: no source bel"
continue
}
set src_bel_pin [get_bel_pins -of_objects $src_cell_pin]
set src_site [get_sites -of_objects $src_bel]
set src_site_type [get_property SITE_TYPE $src_site]
# optional
set src_site_pin [get_site_pins -of_objects $src_cell_pin]
# Report delays with and without interconnect only
foreach ico "0 1" {
if $ico {
set delays [get_net_delays -interconnect_only -of_objects $net]
} else {
set delays [get_net_delays -of_objects $net]
}
set delays_net [expr "$delays_net + [llength $delays]"]
puts $fp "GROUP,$ico,[llength $delays]"
foreach delay $delays {
#set delaystr [get_property NAME $delay]
set fast_max [get_property "FAST_MAX" $delay]
set fast_min [get_property "FAST_MIN" $delay]
set slow_max [get_property "SLOW_MAX" $delay]
set slow_min [get_property "SLOW_MIN" $delay]
# Does this always exist?
set dst_cell_pin_str [get_property TO_PIN $delay]
set dst_cell_pin [get_pins $dst_cell_pin_str]
set dst_cell [get_cells -of_objects $dst_cell_pin]
set dst_bel [get_bels -of_objects $dst_cell]
# WARNING: when there is a passthrough LUT, this can be multiple values
# (one for the real dest pin, and one for the passthrough lut input)
set dst_bel_pin [get_bel_pins -of_objects $dst_cell_pin]
set dst_site [get_sites -of_objects $dst_bel]
set dst_site_type [get_property SITE_TYPE $dst_site]
# optional
set dst_site_pin [get_site_pins -of_objects $dst_cell_pin]
# No fabric on this net?
# don't try querying sites and such
if {[get_nodes -of_objects $net] eq ""} {
if {$src_site ne $dst_site} {
puts "ERROR: site mismatch"
return
}
if {$src_site_type ne $dst_site_type} {
puts "ERROR: site mismatch"
return
}
# Already have everything we could query
# Just output
incr lines_no_int
line_net_internal $fp $net $src_site $src_site_type $src_bel $src_bel_pin $dst_bel $dst_bel_pin $ico $fast_max $fast_min $slow_max $slow_min
# At least some fabric exists
# Does dest BEL exist but not source BEL?
} elseif {$src_bel eq ""} {
puts "ERROR: should have been filtered"
return
# Ideally query from and to cell pins
} else {
# Nested list delimination precedence: ",|:"
# Pips in between
# Walk net, looking for interesting elements in between
# -from <args> - (Optional) Defines the starting points of the pips to get
# from wire or site_pin
set pips [get_pips -of_objects $net -from $src_site_pin -to $dst_site_pin]
# Write pips w/ speed index
foreach pip $pips {
set speed_index [get_property SPEED_INDEX $pip]
lappend pips_out "$pip:$speed_index"
}
set pips_out [list_format "$pips_out" "|"]
set nodes [get_nodes -of_objects $net -from $src_site_pin -to $dst_site_pin]
if {$nodes eq ""} {
puts "ERROR: no nodes"
return
}
set wires [get_wires -of_objects $nodes]
# Write wires
foreach wire $wires {
set speed_index [get_property SPEED_INDEX $wire]
lappend wires_out "$wire:$speed_index"
}
set wires_out [list_format "$wires_out" "|"]
incr lines_some_int
line_net_external $fp $net $src_site $src_site_type $src_site_pin $src_bel $src_bel_pin $dst_site $dst_site_type $dst_site_pin $dst_bel $dst_bel_pin $ico $fast_max $fast_min $slow_max $slow_min $pips_out $wires_out
}
}
}
}
close $fp
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
puts "Took ms: $TIME_taken"
puts "Checked $delays_net delays"
puts "Nets: $nnets"
puts " Skipped (no source cell): $nets_no_src_cell"
puts " Skipped (no source bel): $nets_no_src_bel"
puts "Lines"
puts " No interconnect: $lines_no_int"
puts " Has interconnect: $lines_some_int"
}
# for debugging
# source ../project.tcl
# write_info4

View File

@ -1,247 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, Ar_di2np, loadc_Ads_b, index_names, A_ds2np, simplify_rows, OrderedSet
import numpy as np
import glob
import math
import json
import sympy
from collections import OrderedDict
from fractions import Fraction
def rm_zero_cols(Ads, verbose=True):
removed = OrderedSet()
print('Removing ZERO elements')
for row_ds in Ads:
for k in set(row_ds.keys()):
if k in removed:
del row_ds[k]
elif k.find('ZERO') >= 0:
del row_ds[k]
removed.add(k)
if verbose:
print(' Removing %s' % k)
return removed
def fracr_quick(r):
return [Fraction(numerator=int(x), denominator=1) for x in r]
def fracm_quick(m):
'''Convert integer matrix to Fraction matrix'''
t = type(m[0][0])
print('fracm_quick type: %s' % t)
return [fracr_quick(r) for r in m]
class State(object):
def __init__(self, Ads, zero_names=[]):
self.Ads = Ads
self.names = index_names(self.Ads)
# known zero delay elements
self.zero_names = OrderedSet(zero_names)
# active names in rows
# includes sub variables, excludes variables that have been substituted out
self.base_names = OrderedSet(self.names)
#self.names = OrderedSet(self.base_names)
self.names = set(self.base_names)
# List of variable substitutions
# k => dict of v:n entries that it came from
self.subs = OrderedDict()
self.verbose = True
def print_stats(self):
print("Stats")
print(" Substitutions: %u" % len(self.subs))
if self.subs:
print(
" Largest: %u" % max([len(x) for x in self.subs.values()]))
print(" Rows: %u" % len(self.Ads))
print(
" Cols (in): %u" % (len(self.base_names) + len(self.zero_names)))
print(" Cols (preprocessed): %u" % len(self.base_names))
print(" ZERO names: %u" % len(self.zero_names))
print(" Cols (out): %u" % len(self.names))
print(" Solvable vars: %u" % len(self.names & self.base_names))
assert len(self.names) >= len(self.subs)
@staticmethod
def load(fn_ins, simplify=False, corner=None, rm_zero=False):
zero_names = OrderedSet()
Ads, b = loadc_Ads_b(fn_ins, corner=corner)
if rm_zero:
zero_names = rm_zero_cols(Ads)
if simplify:
print('Simplifying corner %s' % (corner, ))
Ads, b = simplify_rows(Ads, b, remove_zd=False, corner=corner)
return State(Ads, zero_names=zero_names)
def write_state(state, fout):
j = {
'names':
OrderedDict([(x, None) for x in state.names]),
'zero_names':
sorted(list(state.zero_names)),
'base_names':
sorted(list(state.base_names)),
'subs':
OrderedDict([(name, values) for name, values in state.subs.items()]),
'pivots':
state.pivots,
}
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
def row_np2ds(rownp, names):
ret = OrderedDict()
assert len(rownp) == len(names), (len(rownp), len(names))
for namei, name in enumerate(names):
v = rownp[namei]
if v:
ret[name] = v
return ret
def row_sym2dsf(rowsym, names):
'''Convert a sympy row into a dictionary of keys to (numerator, denominator) tuples'''
from sympy import fraction
ret = OrderedDict()
assert len(rowsym) == len(names), (len(rowsym), len(names))
for namei, name in enumerate(names):
v = rowsym[namei]
if v:
(num, den) = fraction(v)
ret[name] = (int(num), int(den))
return ret
def state_rref(state, verbose=False):
print('Converting rows to integer keys')
names, Anp = A_ds2np(state.Ads)
print('np: %u rows x %u cols' % (len(Anp), len(Anp[0])))
mnp = Anp
print('Matrix: %u rows x %u cols' % (len(mnp), len(mnp[0])))
print('Converting np to sympy matrix')
mfrac = fracm_quick(mnp)
# doesn't seem to change anything
#msym = sympy.MutableSparseMatrix(mfrac)
msym = sympy.Matrix(mfrac)
# internal encoding has significnat performance implications
#assert type(msym[0]) is sympy.Integer
if verbose:
print('names')
print(names)
print('Matrix')
sympy.pprint(msym)
print('Making rref')
rref, pivots = msym.rref(normalize_last=False)
if verbose:
print('Pivots')
sympy.pprint(pivots)
print('rref')
sympy.pprint(rref)
state.pivots = OrderedDict()
def row_solved(rowsym, row_pivot):
for ci, c in enumerate(rowsym):
if ci == row_pivot:
continue
if c != 0:
return False
return True
#rrefnp = np.array(rref).astype(np.float64)
#print('Computing groups w/ rref %u row x %u col' % (len(rrefnp), len(rrefnp[0])))
#print(rrefnp)
# rows that have a single 1 are okay
# anything else requires substitution (unless all 0)
# pivots may be fewer than the rows
# remaining rows should be 0s
for row_i, row_pivot in enumerate(pivots):
rowsym = rref.row(row_i)
# yipee! nothign to report
if row_solved(rowsym, row_pivot):
continue
# a grouping
group_name = "GRP_%u" % row_i
rowdsf = row_sym2dsf(rowsym, names)
state.subs[group_name] = rowdsf
# Add the new variables
state.names.add(group_name)
# Remove substituted variables
# Note: variables may appear multiple times
state.names.difference_update(OrderedSet(rowdsf.keys()))
pivot_name = names[row_pivot]
state.pivots[group_name] = pivot_name
if verbose:
print("%s (%s): %s" % (group_name, pivot_name, rowdsf))
return state
def run(fnout, fn_ins, simplify=False, corner=None, rm_zero=False, verbose=0):
print('Loading data')
assert len(fn_ins) > 0
state = State.load(
fn_ins, simplify=simplify, corner=corner, rm_zero=rm_zero)
state_rref(state, verbose=verbose)
state.print_stats()
if fnout:
with open(fnout, 'w') as fout:
write_state(state, fout)
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Compute reduced row echelon (RREF) to form sub.json (variable groups)'
)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--simplify', action='store_true', help='')
parser.add_argument('--corner', default="slow_max", help='')
parser.add_argument(
'--rm-zero', action='store_true', help='Remove ZERO elements')
parser.add_argument(
'--speed-json',
default='build_speed/speed.json',
help='Provides speed index to name translation')
parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
args = parser.parse_args()
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
try:
run(
fnout=args.out,
fn_ins=fns_in,
simplify=args.simplify,
corner=args.corner,
rm_zero=args.rm_zero,
verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,158 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, load_sub
import timfuz
import numpy as np
import math
import sys
import os
import time
import timfuz_solve
import scipy.optimize as optimize
def mkestimate(Anp, b):
'''
Ballpark upper bound estimate assuming variables contribute all of the delay in their respective row
Return the min of all of the occurances
XXX: should this be corner adjusted?
'''
cols = len(Anp[0])
x0 = np.array([1e3 for _x in range(cols)])
for row_np, row_b in zip(Anp, b):
for coli, val in enumerate(row_np):
# favor non-trivial values
if val <= 0:
continue
# Scale by number occurances
ub = row_b / val
if ub <= 0:
continue
if x0[coli] == 0:
x0[coli] = ub
else:
x0[coli] = min(x0[coli], ub)
# reject solutions that don't provide a seed value
# these lead to bad optimizations
assert sum(x0) != 0
return x0
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)
#check_feasible(Anp, b)
'''
Be mindful of signs
Have something like
timing1/timing 2 are constants
delay1 + delay2 + delay4 >= timing1
delay2 + delay3 >= timing2
But need it in compliant form:
-delay1 + -delay2 + -delay4 <= -timing1
-delay2 + -delay3 <= -timing2
'''
rows = len(Anp)
cols = len(Anp[0])
print('Unique delay elements: %d' % len(names))
print('Input paths')
print(' # timing scores: %d' % len(b))
print(' Rows: %d' % rows)
'''
You must have at least as many things to optimize as variables
That is, the system must be plausibly constrained for it to attempt a solve
If not, you'll get a message like
TypeError: Improper input: N=3 must not exceed M=2
'''
if rows < cols:
raise Exception("rows must be >= cols")
tlast = [None]
iters = [0]
printn = [0]
def progress_print():
iters[0] += 1
if tlast[0] is None:
tlast[0] = time.time()
if time.time() - tlast[0] > 1.0:
sys.stdout.write('I:%d ' % iters[0])
tlast[0] = time.time()
printn[0] += 1
if printn[0] % 10 == 0:
sys.stdout.write('\n')
sys.stdout.flush()
def func(params):
progress_print()
return (b - np.dot(Anp, params))
print('')
# Now find smallest values for delay constants
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
print('Running leastsq w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
# starting at 0 completes quicky, but gives a solution near 0 with terrible results
# maybe give a starting estimate to the smallest net delay with the indicated variable
#x0 = np.array([1000.0 for _x in range(cols)])
print('Creating x0 estimate')
x0 = mkestimate(Anp, b)
print('Solving')
res = optimize.least_squares(func, x0, bounds=(0, float('inf')))
print('Done')
if outfn:
timfuz_solve.solve_save(outfn, res.x, names, corner, verbose=verbose)
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Solve timing solution using least squares objective function')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument(
'--massage',
action='store_true',
help='Derive additional constraints to improve solution')
parser.add_argument(
'--sub-json', help='Group substitutions to make fully ranked')
parser.add_argument('--corner', required=True, default="slow_max", help='')
parser.add_argument(
'--out', default=None, help='output timing delay .json')
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
timfuz_solve.run(
run_corner=run_corner,
sub_json=sub_json,
fns_in=fns_in,
corner=args.corner,
massage=args.massage,
outfn=args.out,
verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,193 +0,0 @@
#!/usr/bin/env python3
import scipy.optimize as optimize
from timfuz import Benchmark, load_sub, A_ub_np2d, acorner2csv, corner_s2i
import numpy as np
import glob
import json
import math
import sys
import os
import time
import timfuz_solve
def run_corner(
Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None):
if len(Anp) == 0:
print('WARNING: zero equations')
if outfn:
timfuz_solve.solve_save(outfn, [], [], corner)
return
maxcorner = {
'slow_max': True,
'slow_min': False,
'fast_max': True,
'fast_min': False,
}[corner]
# 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)
#check_feasible(Anp, b)
'''
Be mindful of signs
t1, t2: total delay contants
d1, d2..: variables to solve for
Max corner intuitive form:
d1 + d2 + d4 >= t1
d2 + d3 >= t2
But need it in compliant form:
-d1 + -d2 + -d4 <= -t1
-d2 + -d3 <= -t2
Minimize delay elements
Min corner intuitive form:
d1 + d2 + d4 <= t1
d2 + d3 <= t2
Maximize delay elements
'''
rows = len(Anp)
cols = len(Anp[0])
if maxcorner:
print('maxcorner => scaling to solution form...')
b_ub = -1.0 * b
#A_ub = -1.0 * Anp
A_ub = [-1.0 * x for x in Anp]
else:
print('mincorner => no scaling required')
b_ub = b
A_ub = Anp
print('Creating misc constants...')
# Minimization function scalars
# Treat all logic elements as equally important
if maxcorner:
# Best result are min delays
c = [1 for _i in range(len(names))]
else:
# Best result are max delays
c = [-1 for _i in range(len(names))]
# Delays cannot be negative
# (this is also the default constraint)
#bounds = [(0, None) for _i in range(len(names))]
# Also you can provide one to apply to all
bounds = (0, None)
# Seems to take about rows + 3 iterations
# Give some margin
#maxiter = int(1.1 * rows + 100)
#maxiter = max(1000, int(1000 * rows + 1000))
# Most of the time I want it to just keep going unless I ^C it
maxiter = 1000000
if verbose >= 2:
print('b_ub', b)
print('Unique delay elements: %d' % len(names))
print(' # delay minimization weights: %d' % len(c))
print(' # delay constraints: %d' % len(bounds))
print('Input paths')
print(' # timing scores: %d' % len(b))
print(' Rows: %d' % rows)
tlast = [time.time()]
iters = [0]
printn = [0]
def callback(xk, **kwargs):
iters[0] = xk['nit']
if time.time() - tlast[0] > 1.0:
sys.stdout.write('I:%d ' % xk['nit'])
tlast[0] = time.time()
printn[0] += 1
if printn[0] % 10 == 0:
sys.stdout.write('\n')
sys.stdout.flush()
print('')
# Now find smallest values for delay constants
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
# https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html
print('Running linprog w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
res = optimize.linprog(
c,
A_ub=A_ub,
b_ub=b_ub,
bounds=bounds,
callback=callback,
options={
"disp": True,
'maxiter': maxiter,
'bland': True,
'tol': 1e-6,
})
nonzeros = 0
print('Ran %d iters' % iters[0])
if res.success:
print('Result sample (%d elements)' % (len(res.x)))
plim = 3
for xi, (name, x) in enumerate(zip(names, res.x)):
nonzero = x >= 0.001
if nonzero:
nonzeros += 1
#if nonzero and (verbose >= 1 or xi > 30):
if nonzero and (verbose or (
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
print(' % 4u % -80s % 10.1f' % (xi, name, x))
print('Delay on %d / %d' % (nonzeros, len(res.x)))
if outfn:
timfuz_solve.solve_save(
outfn, res.x, names, corner, verbose=verbose)
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Solve timing solution using linear programming inequalities')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--massage', action='store_true', help='')
parser.add_argument(
'--bounds-csv', help='Previous solve result starting point')
parser.add_argument(
'--sub-json', help='Group substitutions to make fully ranked')
parser.add_argument('--corner', required=True, default="slow_max", help='')
parser.add_argument(
'--out', default=None, help='output timing delay .json')
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None
if args.sub_json:
sub_json = load_sub(args.sub_json)
try:
timfuz_solve.run(
run_corner=run_corner,
sub_json=sub_json,
bounds_csv=args.bounds_csv,
fns_in=fns_in,
corner=args.corner,
massage=args.massage,
outfn=args.out,
verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,60 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, load_sub, load_bounds, loadc_Ads_b
import numpy as np
def run(fns_in, corner, bounds_csv, verbose=False):
print('Loading data')
Ads, borig = loadc_Ads_b(fns_in, corner)
bounds = load_bounds(bounds_csv, corner)
# verify is flattened
for k in bounds.keys():
assert 'GROUP_' not in k, 'Must operate on flattened bounds'
# compute our timing model delay at the given corner
bgots = []
for row_ds in Ads:
delays = [n * bounds[x] for x, n in row_ds.items()]
bgots.append(sum(delays))
ses = (np.asarray(bgots) - np.asarray(borig))**2
mse = (ses).mean(axis=None)
print('MSE aggregate: %0.1f' % mse)
print('Min SE: %0.1f' % min(ses))
print('Max SE: %0.1f' % max(ses))
def main():
import argparse
parser = argparse.ArgumentParser(description='Report a timing fit score')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--corner', required=True, default="slow_max", help='')
parser.add_argument(
'--bounds-csv',
required=True,
help='Previous solve result starting point')
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4i.csv')
try:
run(
fns_in=fns_in,
corner=args.corner,
bounds_csv=args.bounds_csv,
verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,2 +0,0 @@
build

View File

@ -1,12 +0,0 @@
all: build/speed.json
build/node.txt: speed_json.py generate.tcl
mkdir -p build
cd build && ${XRAY_VIVADO} -mode batch -source ../generate.tcl
build/speed.json: build/node.txt
cd build && python3 ../speed_json.py speed_model.txt node.txt speed.json
clean:
rm -rf build

View File

@ -1,4 +0,0 @@
Generates speed.json, describing speed index to string translation.
These indices appear to be fixed between runtimes.
It is unknown how stable they are across versions.

View File

@ -1,172 +0,0 @@
proc pin_info {pin} {
set cell [get_cells -of_objects $pin]
set bel [get_bels -of_objects $cell]
set site [get_sites -of_objects $bel]
return "$site $bel"
}
proc pin_bel {pin} {
set cell [get_cells -of_objects $pin]
set bel [get_bels -of_objects $cell]
return $bel
}
proc build_design_full {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
read_verilog ../../src/picorv32.v
synth_design -top top
#set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
# [get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
place_design
route_design
write_checkpoint -force design.dcp
write_bitstream -force design.bit
}
proc build_design_synth {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
read_verilog ../picorv32.v
synth_design -top top
}
# WARNING: [Common 17-673] Cannot get value of property 'FORWARD' because this property is not valid in conjunction with other property setting on this object.
# WARNING: [Common 17-673] Cannot get value of property 'REVERSE' because this property is not valid in conjunction with other property setting on this object.
proc speed_models1 {} {
set outdir "."
set fp [open "$outdir/speed_model.txt" w]
# list_property [lindex [get_speed_models] 0]
set speed_models [get_speed_models]
set properties [list_property [lindex $speed_models 0]]
# "CLASS DELAY FAST_MAX FAST_MIN IS_INSTANCE_SPECIFIC NAME NAME_LOGICAL SLOW_MAX SLOW_MIN SPEED_INDEX TYPE"
puts $fp $properties
set needspace 0
foreach speed_model $speed_models {
foreach property $properties {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp [get_property $property $speed_model]
set needspace 1
}
puts $fp ""
}
close $fp
}
proc speed_models2 {} {
set outdir "."
set fp [open "$outdir/speed_model.txt" w]
# list_property [lindex [get_speed_models] 0]
set speed_models [get_speed_models]
puts "Items: [llength $speed_models]"
set needspace 0
# Not all objects have the same properties
# But they do seem to have the same list
set properties [list_property [lindex $speed_models 0]]
foreach speed_model $speed_models {
set needspace 0
foreach property $properties {
set val [get_property $property $speed_model]
if {"$val" ne ""} {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp "$property:$val"
set needspace 1
}
}
puts $fp ""
}
close $fp
}
# For cost codes
# Items: 2663055s
# Hmm too much
# Lets filter out items we really want
proc nodes_all {} {
set outdir "."
set fp [open "$outdir/node_all.txt" w]
set items [get_nodes]
puts "Items: [llength $items]"
set needspace 0
set properties [list_property [lindex $items 0]]
foreach item $items {
set needspace 0
foreach property $properties {
set val [get_property $property $item]
if {"$val" ne ""} {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp "$property:$val"
set needspace 1
}
}
puts $fp ""
}
close $fp
}
# Only writes out items with unique cost codes
# (much faster)
proc nodes_unique_cc {} {
set outdir "."
set fp [open "$outdir/node.txt" w]
set items [get_nodes]
puts "Computing cost codes with [llength $items] items"
set needspace 0
set properties [list_property [lindex $items 0]]
set cost_codes_known [dict create]
set itemi 0
foreach item $items {
incr itemi
set cost_code [get_property COST_CODE $item]
if {[ dict exists $cost_codes_known $cost_code ]} {
continue
}
puts " Adding cost code $cost_code @ item $itemi"
dict set cost_codes_known $cost_code 1
set needspace 0
foreach property $properties {
set val [get_property $property $item]
if {"$val" ne ""} {
if $needspace {
puts -nonewline $fp " "
}
puts -nonewline $fp "$property:$val"
set needspace 1
}
}
puts $fp ""
}
close $fp
}
build_design_full
speed_models2
nodes_unique_cc

View File

@ -1,94 +0,0 @@
import json
def load_speed(fin):
speed_models = {}
speed_types = {}
for l in fin:
delay = {}
l = l.strip()
for kvs in l.split():
name, value = kvs.split(':')
name = name.lower()
if name in ('class', ):
continue
if name in ('speed_index', ):
value = int(value)
if name == 'type':
speed_types.setdefault(value, {})
delay[name] = value
delayk = delay['name']
if delayk in delay:
raise Exception("Duplicate name")
if "name" in delay and "name_logical" in delay:
# Always true
if delay['name'] != delay['name_logical']:
raise Exception("nope!")
# Found a counter example
if 0 and delay['name'] != delay['forward']:
# ('BSW_NONTLFW_TLRV', '_BSW_LONG_NONTLFORWARD')
print(delay['name'], delay['forward'])
raise Exception("nope!")
# Found a counter example
if 0 and delay['forward'] != delay['reverse']:
# _BSW_LONG_NONTLFORWARD _BSW_LONG_TLREVERSE
print(delay['forward'], delay['reverse'])
raise Exception("nope!")
speed_models[delayk] = delay
return speed_models, speed_types
def load_cost_code(fin):
# COST_CODE:4 COST_CODE_NAME:SLOWSINGLE
cost_codes = {}
for l in fin:
lj = {}
l = l.strip()
for kvs in l.split():
name, value = kvs.split(':')
name = name.lower()
lj[name] = value
cost_code = {
'name': lj['cost_code_name'],
'code': int(lj['cost_code']),
# Hmm is this unique per type?
#'speed_class': int(lj['speed_class']),
}
cost_codes[cost_code['name']] = cost_code
return cost_codes
def run(speed_fin, node_fin, fout, verbose=0):
print('Loading data')
speed_models, speed_types = load_speed(speed_fin)
cost_codes = load_cost_code(node_fin)
j = {
'speed_model': speed_models,
'speed_type': speed_types,
'cost_code': cost_codes,
}
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Timing fuzzer')
parser.add_argument('--verbose', type=int, help='')
parser.add_argument(
'speed_fn_in', default='/dev/stdin', nargs='?', help='Input file')
parser.add_argument(
'node_fn_in', default='/dev/stdin', nargs='?', help='Input file')
parser.add_argument(
'fn_out', default='/dev/stdout', nargs='?', help='Output file')
args = parser.parse_args()
run(
open(args.speed_fn_in, 'r'),
open(args.node_fn_in, 'r'),
open(args.fn_out, 'w'),
verbose=args.verbose)

View File

@ -1,8 +0,0 @@
module top(input clk, stb, di, output do);
reg dor;
always @(posedge clk) begin
dor <= stb & di;
end
assign do = dor;
endmodule

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark
import json
def write_state(state, fout):
j = {
'names': dict([(x, None) for x in state.names]),
'drop_names': list(state.drop_names),
'base_names': list(state.base_names),
'subs': dict([(name, values) for name, values in state.subs.items()]),
'pivots': state.pivots,
}
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
def gen_rows(fn_ins):
for fn_in in fn_ins:
try:
print('Loading %s' % fn_in)
j = json.load(open(fn_in, 'r'))
group0 = list(j['subs'].values())[0]
value0 = list(group0.values())[0]
if type(value0) is float:
print("WARNING: skipping old format JSON")
continue
else:
print("Value OK")
for sub in j['subs'].values():
row_ds = {}
# TODO: convert to gcd
# den may not always be 0
# lazy solution...just multiply out all the fractions
n = 1
for _var, (_num, den) in sub.items():
n *= den
for var, (num, den) in sub.items():
num2 = n * num
assert num2 % den == 0
row_ds[var] = num2 / den
yield row_ds
except:
print("Error processing %s" % fn_in)
raise
def run(fnout, fn_ins, verbose=0):
print('Loading data')
with open(fnout, 'w') as fout:
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
for row_ds in gen_rows(fn_ins):
ico = '1'
out_b = [1e9, 1e9, 1e9, 1e9]
items = [ico, ' '.join(['%u' % x for x in out_b])]
for k, v in sorted(row_ds.items()):
items.append('%i %s' % (v, k))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Convert substitution groups into .csv to allow incremental rref results'
)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--out', help='Output csv')
parser.add_argument('fns_in', nargs='+', help='sub.json input files')
args = parser.parse_args()
bench = Benchmark()
fns_in = args.fns_in
try:
run(fnout=args.out, fn_ins=args.fns_in, verbose=args.verbose)
finally:
print('Exiting after %s' % bench)
if __name__ == '__main__':
main()

View File

@ -1,156 +0,0 @@
#!/usr/bin/env python3
import timfuz
from timfuz import loadc_Ads_bs, Ads2bounds, PREFIX_W, PREFIX_P, PREFIX_SW_EI, PREFIX_SW_EO, PREFIX_SW_I, sw_ei_s2vals, sw_eo_s2vals, sw_i_s2vals
import sys
import os
import time
import json
def add_pip_wire(tilej, bounds, verbose=False):
'''
We know all possible pips and wires from tilej
Iterate over them and see if a result was generated
'''
used_bounds = set()
for tile in tilej['tiles'].values():
def addk(pws, prefix, k, v):
variable = prefix + ':' + v
val = bounds.get(variable, None)
# print(variable, val)
if val:
used_bounds.add(variable)
else:
val = [None, None, None, None]
pws[k] = val
pips = tile['pips']
for k, v in pips.items():
#pips[k] = bounds.get('PIP_' + v, [None, None, None, None])
addk(pips, PREFIX_P, k, v)
wires = tile['wires']
for k, v in wires.items():
#wires[k] = bounds.get('WIRE_' + v, [None, None, None, None])
addk(wires, PREFIX_W, k, v)
# verify all the variables that should be used were applied
# ...except tilecon may be an ROI and we solved everything
print(
"Interconnect: %u / %u variables used" %
(len(used_bounds), len(bounds)))
if verbose:
print('Remainder: %s' % (set(bounds.keys()) - used_bounds))
def add_sites(tilej, bounds):
# XXX: no source of truth currently
# is there a way we could get this?
# Naming discussion https://github.com/SymbiFlow/prjxray/pull/126
sitej = tilej.setdefault('sites', {})
def add_ei():
for variable, bound in bounds[PREFIX_SW_EI].items():
site_type, src_site_pin, dst_bel_type, dst_bel_pin = sw_ei_s2vals(
variable)
# ex: SLICEL:AX->AFF.D
k = (
'%s:%s->%s.%s' %
(site_type, src_site_pin, dst_bel_type, dst_bel_pin))
sitej.setdefault(site_type, {})[k] = bound
def add_eo():
for variable, bound in bounds[PREFIX_SW_EO].items():
site_type, src_bel_type, src_bel_pin, dst_site_pin = sw_eo_s2vals(
variable)
# ex: SLICEL:AFF.Q->AQ
k = (
'%s:%s.%s->%s' %
(site_type, src_bel_type, src_bel_pin, dst_site_pin))
sitej.setdefault(site_type, {})[k] = bound
def add_i():
for variable, bound in bounds[PREFIX_SW_I].items():
site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin = sw_i_s2vals(
variable)
# ex: SLICEL:LUT6.O2->AFF.D
k = (
'%s:%s.%s->%s.%s' %
(site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin))
sitej.setdefault(site_type, {})[k] = bound
add_ei()
add_eo()
add_i()
print('Sites: added %u sites' % len(sitej))
print('Added site wires')
print(' Site external in: %u' % len(bounds[PREFIX_SW_EI]))
print(' Site external out: %u' % len(bounds[PREFIX_SW_EO]))
print(' Site internal: %u' % len(bounds[PREFIX_SW_I]))
def sep_bounds(bounds):
pw = {}
sites = {PREFIX_SW_EI: {}, PREFIX_SW_EO: {}, PREFIX_SW_I: {}}
for k, v in bounds.items():
prefix = k.split(':')[0]
if prefix in (PREFIX_W, PREFIX_P):
pw[k] = v
elif prefix in (PREFIX_SW_EI, PREFIX_SW_EO, PREFIX_SW_I):
sites[prefix][k] = v
else:
assert 0, 'Unknown delay: %s %s' % (k, prefix)
return pw, sites
def run(fns_in, fnout, tile_json_fn, verbose=False):
# modified in place
tilej = json.load(open(tile_json_fn, 'r'))
for fnin in fns_in:
Ads, bs = loadc_Ads_bs([fnin])
bounds = Ads2bounds(Ads, bs)
bounds_pw, bounds_sites = sep_bounds(bounds)
print(len(bounds), len(bounds_pw), len(bounds_sites))
add_pip_wire(tilej, bounds_pw)
add_sites(tilej, bounds_sites)
timfuz.tilej_stats(tilej)
json.dump(
tilej,
open(fnout, 'w'),
sort_keys=True,
indent=4,
separators=(',', ': '))
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Substitute timgrid timing model names for real timing values')
parser.add_argument(
'--timgrid-s',
default='../../timgrid/build/timgrid-s.json',
help='tilegrid timing delay symbolic input (timgrid-s.json)')
parser.add_argument(
'--out',
default='build/timgrid-vc.json',
help='tilegrid timing delay values at corner (timgrid-vc.json)')
parser.add_argument(
'fn_ins', nargs='+', help='Input flattened timing csv (flat.csv)')
args = parser.parse_args()
run(args.fn_ins, args.out, args.timgrid_s, verbose=False)
if __name__ == '__main__':
main()

View File

@ -1,844 +0,0 @@
#!/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)

View File

@ -1,322 +0,0 @@
#!/usr/bin/env python3
from timfuz import simplify_rows, print_eqns, print_eqns_np, sort_equations, col_dist, index_names
import numpy as np
import math
import sys
import datetime
import os
import time
import copy
from collections import OrderedDict
def lte_const(row_ref, row_cmp):
'''Return true if all constants are smaller magnitude in row_cmp than row_ref'''
#return False
for k, vc in row_cmp.items():
vr = row_ref.get(k, None)
# Not in reference?
if vr is None:
return False
if vr < vc:
return False
return True
def sub_rows(row_ref, row_cmp):
'''Do row_ref - row_cmp'''
#ret = {}
ret = OrderedDict()
ks = set(row_ref.keys())
ks.update(set(row_cmp.keys()))
for k in ks:
vr = row_ref.get(k, 0)
vc = row_cmp.get(k, 0)
res = vr - vc
if res:
ret[k] = res
return ret
def derive_eq_by_row(Ads, b, verbose=0, col_lim=0, cmp_heuristic=True):
'''
Derive equations by subtracting whole rows
cmp_heuristic: drop large generated rows since these are unlikely to constrain the solution
Given equations like:
t0 >= 10
t0 + t1 >= 15
t0 + t1 + t2 >= 17
When I look at these, I think of a solution something like:
t0 = 10f
t1 = 5
t2 = 2
However, linprog tends to choose solutions like:
t0 = 17
t1 = 0
t2 = 0
To this end, add additional constraints by finding equations that are subsets of other equations
How to do this in a reasonable time span?
Also equations are sparse, which makes this harder to compute
'''
assert len(Ads) == len(b), 'Ads, b length mismatch'
rows = len(Ads)
# Index equations into hash maps so can lookup sparse elements quicker
assert len(Ads) == len(b)
Ads_ret = copy.copy(Ads)
assert len(Ads) == len(Ads_ret)
#print('Finding subsets')
ltes = 0
b_ret = list(b)
sys.stdout.write('Deriving rows (%u) ' % rows)
sys.stdout.flush()
progress = int(max(1, rows / 100))
b_warns = 0
for row_refi, row_ref in enumerate(Ads):
if row_refi % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
if col_lim and len(row_ref) > col_lim:
continue
for row_cmpi, row_cmp in enumerate(Ads):
if row_refi == row_cmpi or col_lim and len(row_cmp) > col_lim:
continue
if not lte_const(row_ref, row_cmp):
continue
ltes += 1
if verbose:
print('')
print('match')
print(' ', row_ref, b[row_refi])
print(' ', row_cmp, b[row_cmpi])
# Reduce
row_new = sub_rows(row_ref, row_cmp)
# Did this actually significantly reduce the search space?
# should be a relatively small number of rows and be significantly smaller than at least one of them
def is_smaller_row():
# Keep any generally small rows
if len(row_new) <= 8:
return True
# And anything that reduced at least one row by half
# Ex: going from 120 and 100 element rows to a 20 element row
return len(row_new) <= len(row_cmp) / 2 or len(
row_new) <= len(row_ref) / 2
if cmp_heuristic and not is_smaller_row():
continue
b_new = b[row_refi] - b[row_cmpi]
# Definitely possible
# Maybe filter these out if they occur?
if verbose:
print(b_new)
# Also inverted sign
if b_new < 0:
if verbose:
print("Unexpected b")
b_warns += 1
continue
if verbose:
print('OK')
Ads_ret.append(row_new)
b_ret.append(b_new)
print(' done')
#A_ub_ret = A_di2np(Ads2, cols=cols)
print(
'Derive row: %d => %d rows using %d lte' % (len(b), len(b_ret), ltes))
print('Dropped %u invalid equations' % b_warns)
assert len(Ads_ret) == len(b_ret)
return Ads_ret, b_ret
def derive_eq_by_col(Ads, b_ub, verbose=0, keep_orig=True):
'''
Derive equations by subtracting out all bounded constants (ie "known" columns)
XXX: is this now redundant with derive_row?
Seems like a degenerate case, although maybe quicker
'''
rows = len(Ads)
# Index equations with a single constraint
knowns = {}
sys.stdout.write('Derive col indexing ')
sys.stdout.flush()
progress = max(1, rows / 100)
for row_refi, row_refd in enumerate(Ads):
if row_refi % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
if len(row_refd) == 1:
k, v = list(row_refd.items())[0]
# assume simplify_equations handles de-duplicating
new_b = 1.0 * b_ub[row_refi] / v
assert k not in knowns, "Got new %s w/ val %u, old val %u" % (
k, new_b, knowns[k])
knowns[k] = new_b
print(' done')
#knowns_set = set(knowns.keys())
print('%d constrained' % len(knowns))
'''
Now see what we can do
Rows that are already constrained: eliminate
TODO: maybe keep these if this would violate their constraint
Otherwise eliminate the original row and generate a simplified result now
'''
b_ret = []
Ads_ret = []
sys.stdout.write('Derive col main ')
sys.stdout.flush()
progress = max(1, rows / 100)
for row_refi, row_refd in enumerate(Ads):
if row_refi % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
b_ref = b_ub[row_refi]
if keep_orig:
Ads_ret.append(row_refd)
b_ret.append(b_ref)
# Reduce as much as possible
#row_new = {}
row_new = OrderedDict()
b_new = b_ref
# Copy over single entries
if len(row_refd) == 1:
row_new = row_refd
else:
for k, v in row_refd.items():
if k in knowns:
# Remove column and take out corresponding delay
b_new -= v * knowns[k]
# Copy over
else:
row_new[k] = v
# Possibly reduced all usable contants out
if len(row_new) == 0:
continue
# invalid?
if b_new < 0:
continue
if not keep_orig or row_new != row_refd:
Ads_ret.append(row_new)
b_ret.append(b_new)
print(' done')
print('Derive col: %d => %d rows' % (len(b_ub), len(b_ret)))
return Ads_ret, b_ret
# iteratively increasing column limit until all columns are added
def massage_equations(
Ads, b, verbose=False, corner=None, iter_lim=1, col_lim=100000):
#col_lim = 15
'''
Subtract equations from each other to generate additional constraints
Helps provide additional guidance to solver for realistic delays
Ex: given:
a >= 10
a + b >= 100
A valid solution is:
a = 100
However, a better solution is something like
a = 10
b = 90
This creates derived constraints to provide more realistic results
Equation pipeline
Some operations may generate new equations
Simplify after these to avoid unnecessary overhead on redundant constraints
Similarly some operations may eliminate equations, potentially eliminating a column (ie variable)
Remove these columns as necessary to speed up solving
'''
assert len(Ads) == len(b), 'Ads, b length mismatch'
def check_cols():
assert len(index_names(Ads)) == cols
def debug(what):
check_cols()
if 1 or verbose:
print('')
print_eqns(Ads, b, verbose=verbose, label=what, lim=20)
col_dist(Ads, what)
#check_feasible_d(Ads, b)
# Try to (intelligently) subtract equations to generate additional constraints
# This helps avoid putting all delay in a single shared variable
dstart = len(b)
cols = len(index_names(Ads))
# Each iteration one more column is allowed until all columns are included
# (and the system is stable)
di = 0
while True:
print
n_orig = len(b)
print('Loop %d, lim %d' % (di + 1, col_lim))
# Meat of the operation
Ads, b = derive_eq_by_row(Ads, b, col_lim=col_lim, cmp_heuristic=True)
debug("der_rows")
# Run another simplify pass since new equations may have overlap with original
Ads, b = simplify_rows(Ads, b, corner=corner)
print('Derive row: %d => %d equations' % (n_orig, len(b)))
debug("der_rows simp")
# derive_cols is mostly degenerate case of derive_rows
# however, it will sub out single variables a lot faster if there are a lot of them
# linear vs above quadratic, might as well keep it in
if 1:
n_orig2 = len(b)
# Meat of the operation
Ads, b = derive_eq_by_col(Ads, b)
debug("der_cols")
# Run another simplify pass since new equations may have overlap with original
Ads, b = simplify_rows(Ads, b, corner=corner)
print(
'Derive col %d: %d => %d equations' %
(di + 1, n_orig2, len(b)))
debug("der_cols simp")
# Doesn't help computation, but helps debugging
Ads, b = sort_equations(Ads, b)
debug("loop done")
col_dist(Ads, 'derive done iter %d, lim %d' % (di, col_lim), lim=12)
rows = len(Ads)
di += 1
dend = len(b)
# possible that a new equation was generated and taken away, but close enough
if n_orig == len(b) and col_lim >= cols or di >= iter_lim:
break
col_lim += col_lim / 5
print('')
print('Derive net: %d => %d' % (dstart, dend))
print('')
# Was experimentting to see how much the higher order columns really help
# Helps debug readability
Ads, b = sort_equations(Ads, b)
debug("final (sorted)")
print('')
print('Massage final: %d => %d rows' % (dstart, dend))
cols_end = len(index_names(Ads))
print('Massage final: %d => %d cols' % (cols, cols_end))
assert cols_end == cols
return Ads, b

View File

@ -1,237 +0,0 @@
#!/usr/bin/env python3
from timfuz import simplify_rows, loadc_Ads_b, index_names, A_ds2np, run_sub_json, print_eqns, Ads2bounds, instances, SimplifiedToZero, allow_zero_eqns, corner_s2i, acorner2csv
from timfuz_massage import massage_equations
import numpy as np
import sys
import math
def check_feasible(A_ub, b_ub):
'''
Put large timing constants into the equations
See if that would solve it
Its having trouble giving me solutions as this gets bigger
Make a terrible baseline guess to confirm we aren't doing something bad
'''
sys.stdout.write('Check feasible ')
sys.stdout.flush()
rows = len(b_ub)
cols = len(A_ub[0])
progress = max(1, rows / 100)
'''
Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
Series can have several hundred delay elements
Max delay in ballpark
'''
xs = [1e9 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 filter_bounds(Ads, b, bounds, corner):
'''
Given min variable delays, remove rows that won't constrain solution
Ex for max corner:
Given bounds:
a >= 10
b >= 1
c >= 0
Given equations:
a + b >= 10
a + c >= 100
The first equation is already satisfied
However, the second needs either an increase in a or an increase in c '''
if 'max' in corner:
# Keep delays possibly larger than current bound
def keep(row_b, est):
return row_b > est
T_UNK = 0
elif 'min' in corner:
# Keep delays possibly smaller than current bound
def keep(row_b, est):
return row_b < est
T_UNK = 1e9
else:
assert 0
ret_Ads = []
ret_b = []
unknowns = set()
for row_ds, row_b in zip(Ads, b):
# some variables get estimated at 0
def getvar(k):
#return bounds.get(k, T_UNK)
ret = bounds.get(k, None)
if ret is not None:
return ret
unknowns.add(k)
return T_UNK
est = sum([getvar(k) * v for k, v in row_ds.items()])
# will this row potentially constrain us more?
if keep(row_b, est):
ret_Ads.append(row_ds)
ret_b.append(row_b)
if len(unknowns):
print('WARNING: %u encountered undefined bounds' % len(unknowns))
return ret_Ads, ret_b
def solve_save(outfn, xvals, names, corner, save_zero=True, verbose=False):
# 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]
roundf = {
'fast_max': math.ceil,
'fast_min': math.floor,
'slow_max': math.ceil,
'slow_min': math.floor,
}[corner]
print('Writing results')
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(xvals, names):
row_ico = 1
if xval < delta:
if verbose:
print('WARNING: near 0 delay on %s: %0.6f' % (name, xval))
zeros += 1
if not save_zero:
continue
items = [str(row_ico), acorner2csv(roundf(xval), corneri)]
items.append('%u %s' % (1, name))
fout.write(','.join(items) + '\n')
nonzeros = len(names) - zeros
print(
'Wrote: %u / %u constrained delays, %u zeros' %
(nonzeros, len(names), zeros))
# max only...min corner seems to like 0
# see https://github.com/SymbiFlow/prjxray/issues/136
if 'max' in corner:
assert nonzeros, 'Failed to estimate delay'
def run(
fns_in,
corner,
run_corner,
sub_json=None,
bounds_csv=None,
dedup=True,
massage=False,
outfn=None,
verbose=False,
**kwargs):
print('Loading data')
Ads, b = loadc_Ads_b(fns_in, corner)
# Remove duplicate rows
# is this necessary?
# maybe better to just add them into the matrix directly
if dedup:
oldn = len(Ads)
iold = instances(Ads)
Ads, b = simplify_rows(Ads, b, corner=corner)
print('Simplify %u => %u rows' % (oldn, len(Ads)))
print('Simplify %u => %u instances' % (iold, instances(Ads)))
if sub_json:
print('Sub: %u rows' % len(Ads))
iold = instances(Ads)
names_old = index_names(Ads)
run_sub_json(Ads, sub_json, verbose=verbose)
names = index_names(Ads)
print("Sub: %u => %u names" % (len(names_old), len(names)))
print('Sub: %u => %u instances' % (iold, instances(Ads)))
else:
names = index_names(Ads)
'''
Substitution .csv
Special .csv containing one variable per line
Used primarily for multiple optimization passes, such as different algorithms or additional constraints
'''
if bounds_csv:
Ads2, b2 = loadc_Ads_b([bounds_csv], corner)
bounds = Ads2bounds(Ads2, b2)
assert len(bounds), 'Failed to load bounds'
rows_old = len(Ads)
Ads, b = filter_bounds(Ads, b, bounds, corner)
print(
'Filter bounds: %s => %s + %s rows' %
(rows_old, len(Ads), len(Ads2)))
Ads = Ads + Ads2
b = b + b2
assert len(Ads) or allow_zero_eqns()
assert len(Ads) == len(b), 'Ads, b length mismatch'
if verbose:
print
print_eqns(Ads, b, verbose=verbose)
#print
#col_dist(A_ubd, 'final', names)
if massage:
try:
Ads, b = massage_equations(Ads, b, corner=corner)
except SimplifiedToZero:
if not allow_zero_eqns():
raise
print('WARNING: simplified to zero equations')
Ads = []
b = []
print('Converting to numpy...')
names, Anp = A_ds2np(Ads)
run_corner(
Anp,
np.asarray(b),
names,
corner,
outfn=outfn,
verbose=verbose,
**kwargs)

View File

@ -1,12 +0,0 @@
all: build/timgrid.json
build/timgrid.txt: generate.tcl
mkdir -p build
cd build && ${XRAY_VIVADO} -mode batch -source ../generate.tcl
build/timgrid.json: build/timgrid.txt
cd build && python3 ../tile_txt2json.py --speed-json ../../speed/build/speed.json timgrid.txt timgrid-s.json
clean:
rm -rf build

View File

@ -1,16 +0,0 @@
#!/bin/bash -x
source ${XRAY_GENHEADER}
${XRAY_VIVADO} -mode batch -source ../generate.tcl
for x in design*.bit; do
${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x
done
for x in design_*.bits; do
diff -u design.bits $x | grep '^[-+]bit' > ${x%.bits}.delta
done
python3 ../generate.py design_*.delta > tilegrid.json

View File

@ -1,118 +0,0 @@
proc build_project {} {
if 0 {
set grid_min_x -1
set grid_max_x -1
set grid_min_y -1
set grid_max_y -1
} {
set grid_min_x $::env(XRAY_ROI_GRID_X1)
set grid_max_x $::env(XRAY_ROI_GRID_X2)
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
}
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
synth_design -top top
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb]
create_pblock roi
add_cells_to_pblock [get_pblocks roi] [get_cells roi]
resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)"
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
set_param tcl.collectionResultDisplayLimit 0
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
set luts [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT]
set selected_luts {}
set lut_index 0
# LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per column)
# Also, if GRID_MIN/MAX is not defined, automatically create it based on used CLBs
# See caveat in README on automatic creation
foreach lut $luts {
set tile [get_tile -of_objects $lut]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
if [expr $grid_min_x < 0 || $grid_x < $grid_min_x] {set grid_min_x $grid_x}
if [expr $grid_max_x < 0 || $grid_x > $grid_max_x] {set grid_max_x $grid_x}
if [expr $grid_min_y < 0 || $grid_y < $grid_min_y] {set grid_min_y $grid_y}
if [expr $grid_max_y < 0 || $grid_y > $grid_max_y] {set grid_max_y $grid_y}
# 50 per column => 50, 100, 150, etc
if [regexp "Y(0|[0-9]*[05]0)/" $lut] {
set cell [get_cells roi/is[$lut_index].lut]
set_property LOC [get_sites -of_objects $lut] $cell
set lut_index [expr $lut_index + 1]
lappend selected_luts $lut
}
}
place_design
route_design
write_checkpoint -force design.dcp
write_bitstream -force design.bit
}
proc write_data {} {
if 0 {
set grid_min_x -1
set grid_max_x -1
set grid_min_y -1
set grid_max_y -1
} {
set grid_min_x $::env(XRAY_ROI_GRID_X1)
set grid_max_x $::env(XRAY_ROI_GRID_X2)
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
}
# Get all tiles in ROI, ie not just the selected LUTs
set tiles [get_tiles -filter "GRID_POINT_X >= $grid_min_x && GRID_POINT_X <= $grid_max_x && GRID_POINT_Y >= $grid_min_y && GRID_POINT_Y <= $grid_max_y"]
# Write tiles.txt with site metadata
set fp [open "timgrid.txt" w]
foreach tile $tiles {
set type [get_property TYPE $tile]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
set items {}
set wires [get_wires -of_objects $tile]
if [llength $wires] {
foreach wire $wires {
set name [get_property NAME $wire]
set speed_index [get_property SPEED_INDEX $wire]
lappend items wire $name $speed_index
}
}
set pips [get_pips -of_objects $tile]
if [llength $pips] {
foreach pip $pips {
set name [get_property NAME $pip]
set speed_index [get_property SPEED_INDEX $pip]
lappend items pip $name $speed_index
}
}
puts $fp "$type $tile $grid_x $grid_y $items"
}
close $fp
}
build_project
write_data

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import time
import json
SI_NONE = 0xFFFF
def load_speed_json(f):
j = json.load(f)
# Index speed indexes to names
speed_i2s = {}
for k, v in j['speed_model'].items():
i = v['speed_index']
if i != SI_NONE:
speed_i2s[i] = k
return j, speed_i2s
def gen_tiles(fnin, speed_i2s):
for l in open(fnin):
# lappend items pip $name $speed_index
# puts $fp "$type $tile $grid_x $grid_y $items"
parts = l.strip().split()
tile_type, tile_name, grid_x, grid_y = parts[0:4]
grid_x, grid_y = int(grid_x), int(grid_y)
tuples = parts[4:]
assert len(tuples) % 3 == 0
pips = {}
wires = {}
for i in range(0, len(tuples), 3):
ttype, name, speed_index = tuples[i:i + 3]
name_local = name.split('/')[1]
{
'pip': pips,
'wire': wires,
}[ttype][name_local] = speed_i2s[int(speed_index)]
yield (tile_type, tile_name, grid_x, grid_y, pips, wires)
def run(fnin, fnout, speed_json_fn, verbose=False):
speedj, speed_i2s = load_speed_json(open(speed_json_fn, 'r'))
tiles = {}
for tile in gen_tiles(fnin, speed_i2s):
(tile_type, tile_name, grid_x, grid_y, pips, wires) = tile
this_dat = {'pips': pips, 'wires': wires}
if tile_type not in tiles:
tiles[tile_type] = this_dat
else:
if tiles[tile_type] != this_dat:
print(tile_name, tile_type)
print(this_dat)
print(tiles[tile_type])
assert 0
j = {'tiles': tiles}
json.dump(
j, open(fnout, 'w'), sort_keys=True, indent=4, separators=(',', ': '))
def main():
import argparse
parser = argparse.ArgumentParser(description='Solve timing solution')
parser.add_argument(
'--speed-json',
default='../../speed/build/speed.json',
help='Provides speed index to name translation')
parser.add_argument('fnin', default=None, help='input tcl output .txt')
parser.add_argument('fnout', default=None, help='output .json')
args = parser.parse_args()
run(args.fnin, args.fnout, speed_json_fn=args.speed_json, verbose=False)
if __name__ == '__main__':
main()

View File

@ -1,49 +0,0 @@
//Need at least one LUT per frame base address we want
`define N 100
module top(input clk, stb, di, output do);
localparam integer DIN_N = 6;
localparam integer DOUT_N = `N;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi (
.clk(clk),
.din(din),
.dout(dout)
);
endmodule
module roi(input clk, input [5:0] din, output [`N-1:0] dout);
genvar i;
generate
for (i = 0; i < `N; i = i+1) begin:is
LUT6 #(
.INIT(64'h8000_0000_0000_0001 + (i << 16))
) lut (
.I0(din[0]),
.I1(din[1]),
.I2(din[2]),
.I3(din[3]),
.I4(din[4]),
.I5(din[5]),
.O(dout[i])
);
end
endgenerate
endmodule

View File

@ -1,62 +0,0 @@
`define N 100
module top(input clk, stb, di, output do);
localparam integer DIN_N = 8;
localparam integer DOUT_N = `N;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
reg [DIN_N-1:0] din_shr;
reg [DOUT_N-1:0] dout_shr;
always @(posedge clk) begin
din_shr <= {din_shr, di};
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
if (stb) begin
din <= din_shr;
dout_shr <= dout;
end
end
assign do = dout_shr[DOUT_N-1];
roi roi (
.clk(clk),
.din(din),
.dout(dout)
);
endmodule
module roi(input clk, input [7:0] din, output [`N-1:0] dout);
genvar i;
generate
for (i = 0; i < `N; i = i+1) begin:is
(* KEEP, DONT_TOUCH *)
RAMB36E1 #(.INIT_00(256'h0000000000000000000000000000000000000000000000000000000000000000)) ram (
.CLKARDCLK(din[0]),
.CLKBWRCLK(din[1]),
.ENARDEN(din[2]),
.ENBWREN(din[3]),
.REGCEAREGCE(din[4]),
.REGCEB(din[5]),
.RSTRAMARSTRAM(din[6]),
.RSTRAMB(din[7]),
.RSTREGARSTREG(din[0]),
.RSTREGB(din[1]),
.ADDRARDADDR(din[2]),
.ADDRBWRADDR(din[3]),
.DIADI(din[4]),
.DIBDI(din[5]),
.DIPADIP(din[6]),
.DIPBDIP(din[7]),
.WEA(din[0]),
.WEBWE(din[1]),
.DOADO(dout[0]),
.DOBDO(),
.DOPADOP(),
.DOPBDOP());
end
endgenerate
endmodule

View File

@ -1,165 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import time
import json
from collections import OrderedDict
import timfuz
corner_s2i = OrderedDict(
[
('fast_max', 0),
('fast_min', 1),
('slow_max', 2),
('slow_min', 3),
])
corner2minmax = {
'fast_max': max,
'fast_min': min,
'slow_max': max,
'slow_min': min,
}
def merge_bdict(vi, vo):
'''
vi: input dictionary
vo: output dictionary
values are corner delay 4 tuples
'''
for name, bis in vi.items():
bos = vo.get(name, [None, None, None, None])
for cornerk, corneri in corner_s2i.items():
bo = bos[corneri]
bi = bis[corneri]
# no new data
if bi is None:
pass
# no previous data
elif bo is None:
bos[corneri] = bi
# combine
else:
minmax = corner2minmax[cornerk]
bos[corneri] = minmax(bi, bo)
def merge_tiles(tileji, tilejo):
'''
{
"tiles": {
"BRKH_B_TERM_INT": {
"pips": {},
"wires": {
"B_TERM_UTURN_INT_ER1BEG0": [
null,
null,
93,
null
],
'''
for tilek, tilevi in tileji.items():
# No previous data? Copy
tilevo = tilejo.get(tilek, None)
if tilevo is None:
tilejo[tilek] = tilevi
# Otherwise combine
else:
merge_bdict(tilevi['pips'], tilevo['pips'])
merge_bdict(tilevi['wires'], tilevo['wires'])
def merge_sites(siteji, sitejo):
for k, vi in siteji.items():
vo = sitejo.get(k, None)
# No previous data? Copy
if vo is None:
sitejo[k] = vi
# Otherwise combine
else:
merge_bdict(vi, vo)
def build_tilejo(fnins):
# merge all inputs into blank output
tilejo = {"tiles": {}, "sites": {}}
for fnin in fnins:
tileji = json.load(open(fnin, 'r'))
merge_tiles(tileji['tiles'], tilejo['tiles'])
merge_sites(tileji['sites'], tilejo['sites'])
return tilejo
def check_corner_minmax(tilej, verbose=False):
# Post processing pass looking for min/max inconsistencies
# Especially an issue due to complexities around under-constrained elements
# (ex: pivots set to 0 delay)
print('Checking for min/max consistency')
checks = 0
bad = 0
for tilev in tilej['tiles'].values():
def process_type(etype):
nonlocal checks
nonlocal bad
for pipk, pipv in tilev[etype].items():
for corner in ('slow', 'fast'):
mini = corner_s2i[corner + '_min']
minv = pipv[mini]
maxi = corner_s2i[corner + '_max']
maxv = pipv[maxi]
if minv is not None and maxv is not None:
checks += 1
if minv > maxv:
if verbose:
print(
'WARNING: element %s %s min/max adjusted on corner %s'
% (etype, pipk, corner))
bad += 1
pipv[mini] = maxv
pipv[maxi] = minv
process_type('pips')
process_type('wires')
print('')
print('minmax: %u / %u pairs bad pairs adjusted' % (bad, checks))
timfuz.tilej_stats(tilej)
def run(fnins, fnout, verbose=False):
tilejo = build_tilejo(fnins)
check_corner_minmax(tilejo)
# XXX: check fast vs slow?
# check_corners_minmax(tilejo)
json.dump(
tilejo,
open(fnout, 'w'),
sort_keys=True,
indent=4,
separators=(',', ': '))
def main():
import argparse
parser = argparse.ArgumentParser(
description='Combine multiple tile corners into one .json file')
parser.add_argument(
'--out', required=True, help='Combined timgrid-v.json files')
parser.add_argument('fnins', nargs='+', help='Input timgrid-vc.json files')
args = parser.parse_args()
run(args.fnins, args.out, verbose=False)
if __name__ == '__main__':
main()

View File

@ -1,130 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, A_di2ds, PREFIX_W, PREFIX_P
from timing_txt2json import gen_timing4n, load_speed_json
import glob
import math
import json
import sys
from collections import OrderedDict
# Verify the nodes and wires really do line up
def vals2Adi_check(vals, names):
print('Checking')
for val in vals:
node_wires = 0
for _node, wiresn in val['nodes']:
node_wires += wiresn
assert node_wires == len(val['wires'])
print('Done')
assert 0
def row_json2Ads(val, verbose=False):
'''Convert timing4 JSON into Ads interconnect equations'''
def pip2speed(pip):
_site, _name, pip_name = pip
return PREFIX_P + ':' + pip_name
def wire2speed(wire):
_site, _name, wire_name = wire
return PREFIX_W + ':' + wire_name
def add_name(name):
row_ds[name] = row_ds.get(name, 0) + 1
row_ds = {}
for pip in val['pips']:
add_name(pip2speed(pip))
for wire in val['wires']:
add_name(wire2speed(wire))
return row_ds
def load_Ads_gen(speed_json_f, fn_ins):
print('Loading data')
_speedj, speed_i2s = load_speed_json(speed_json_f)
for fn_in in fn_ins:
def mkb(val):
t = val['t']
return (t['fast_max'], t['fast_min'], t['slow_max'], t['slow_min'])
for val in gen_timing4n(fn_in, speed_i2s):
row_ds = row_json2Ads(val)
row_bs = mkb(val)
row_ico = val['ico']
yield row_bs, row_ds, row_ico
def run(speed_json_f, fout, fns_in, verbose=0, corner=None):
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
for row_bs, row_ds, row_ico in load_Ads_gen(speed_json_f, fns_in):
# XXX: consider removing ico column
# its obsolete at this point
if not row_ico:
continue
# like: 123 456 120 450, 1 a, 2 b
# first column has delay corners, followed by delay element count
items = [str(row_ico), ' '.join([str(x) for x in row_bs])]
for k, v in sorted(row_ds.items()):
items.append('%u %s' % (v, k))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Convert obscure timing4.txt into timing4i.csv (interconnect delay variable occurances)'
)
parser.add_argument('--verbose', type=int, help='')
# made a bulk conversion easier...keep?
parser.add_argument(
'--auto-name', action='store_true', help='timing4.txt => timing4i.csv')
parser.add_argument(
'--speed-json',
default='build_speed/speed.json',
help='Provides speed index to name translation')
parser.add_argument('--out', default=None, help='Output timing4i.csv file')
parser.add_argument('fns_in', nargs='+', help='Input timing4.txt files')
args = parser.parse_args()
bench = Benchmark()
fnout = args.out
if fnout is None:
if args.auto_name:
assert len(args.fns_in) == 1
fnin = args.fns_in[0]
fnout = fnin.replace('.txt', 'i.csv')
assert fnout != fnin, 'Expect .txt in'
else:
# practically there are too many stray prints to make this work as expected
assert 0, 'File name required'
fnout = '/dev/stdout'
print("Writing to %s" % fnout)
fout = open(fnout, 'w')
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4.txt')
run(
speed_json_f=open(args.speed_json, 'r'),
fout=fout,
fns_in=fns_in,
verbose=args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,201 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, A_di2ds
import glob
import math
import json
import sys
from collections import OrderedDict
# Speed index: some sort of special value
SI_NONE = 0xFFFF
def parse_pip(s, speed_i2s):
# Entries like
# CLK_BUFG_REBUF_X60Y117/CLK_BUFG_REBUF.CLK_BUFG_REBUF_R_CK_GCLK0_BOT<<->>CLK_BUFG_REBUF_R_CK_GCLK0_TOP
# Convert to (site, type, pip_junction, pip)
pipstr, speed_index = s.split(':')
speed_index = int(speed_index)
site, instance = pipstr.split('/')
#type, pip_junction, pip = others.split('.')
#return (site, type, pip_junction, pip)
return site, instance, speed_i2s[int(speed_index)]
def parse_pips(pips, speed_i2s):
if not pips:
return []
return [parse_pip(pip, speed_i2s) for pip in pips.split('|')]
def parse_wire(s, speed_i2s):
# CLBLM_R_X3Y80/CLBLM_M_D6:952
wirestr, speed_index = s.split(':')
site, instance = wirestr.split('/')
return site, instance, speed_i2s[int(speed_index)]
def parse_wires(wires, speed_i2s):
if not wires:
return []
return [parse_wire(wire, speed_i2s) for wire in wires.split('|')]
def gen_timing4(fn, speed_i2s):
f = open(fn, 'r')
header_want = "linetype,net,src_site,src_site_type,src_site_pin,src_bel,src_bel_pin,dst_site,dst_site_type,dst_site_pin,dst_bel,dst_bel_pin,ico,fast_max,fast_min,slow_max,slow_min,pips,wires"
ncols = len(header_want.split(','))
# src_bel dst_bel ico fast_max fast_min slow_max slow_min pips
header_got = f.readline().strip()
if header_got != header_want:
raise Exception("Unexpected columns")
rets = 0
# XXX: there were malformed lines, but think they are fixed now?
bads = 0
net_lines = 0
for l in f:
def group_line():
ncols = len('lintype,ico,delays'.split(','))
assert len(parts) == ncols
_lintype, ico, delays = parts
return int(ico), int(delays)
def net_line():
assert len(parts) == ncols, "Expected %u parts, got %u" % (
ncols, len(parts))
_lintype, net, src_site, src_site_type, src_site_pin, src_bel, src_bel_pin, dst_site, dst_site_type, dst_site_pin, dst_bel, dst_bel_pin, ico, fast_max, fast_min, slow_max, slow_min, pips, wires = parts
def filt_passthru_lut(bel_pins):
'''
Ex: SLICE_X11Y110/A6LUT/A6 SLICE_X11Y110/AFF/D
'''
parts = bel_pins.split()
if len(parts) == 1:
return parts[0]
else:
assert len(parts) == 2
# the LUT shoudl always go first?
bel_pin_lut, bel_pin_dst = parts
assert '6LUT' in bel_pin_lut
return bel_pin_dst
return {
'net': net,
'src': {
'site': src_site,
'site_type': src_site_type,
'site_pin': src_site_pin,
'bel': src_bel,
'bel_pin': src_bel_pin,
},
'dst': {
'site': dst_site,
'site_type': dst_site_type,
'site_pin': dst_site_pin,
'bel': dst_bel,
'bel_pin': filt_passthru_lut(dst_bel_pin),
},
't': {
# ps
'fast_max': int(fast_max),
'fast_min': int(fast_min),
'slow_max': int(slow_max),
'slow_min': int(slow_min),
},
'ico': int(ico),
'pips': parse_pips(pips, speed_i2s),
'wires': parse_wires(wires, speed_i2s),
'line': l,
}
l = l.strip()
if not l:
continue
parts = l.split(',')
lintype = parts[0]
val = {
'NET': net_line,
'GROUP': group_line,
}[lintype]()
yield lintype, val
rets += 1
print(' load %s: %d bad, %d good lines' % (fn, bads, rets))
def gen_timing4n(fn, speed_i2s):
'''Only generate nets'''
for lintype, val in gen_timing4(fn, speed_i2s):
if lintype == 'NET':
yield val
def gen_timing4a(fn, speed_i2s):
'''
Like above, but aggregate ico + non-ico into single entries
Key these based on uniqueness of (src_bel, dst_bel)
ico 0 is followed by 1
They should probably even be in the same order
Maybe just assert that?
'''
entries = {}
timgen = gen_timing4(fn, speed_i2s)
rets = 0
while True:
def get_ico(exp_ico):
ret = []
try:
lintype, val = next(timgen)
except StopIteration:
return None
assert lintype == 'GROUP'
ico, delays = val
assert ico == exp_ico
for _ in range(delays):
lintype, val = next(timgen)
assert lintype == 'NET'
ret.append(val)
return ret
ico0s = get_ico(0)
if ico0s is None:
break
ico1s = get_ico(1)
# TODO: verify this is actually true
assert len(ico0s) == len(ico1s)
def same_path(l, r):
# if source and dest are the same, should be the same thing
return l['src']['bel_pin'] == r['src']['bel_pin'] and l['dst'][
'bel_pin'] == r['dst']['bel_pin']
for ico0, ico1 in zip(ico0s, ico1s):
# TODO: verify this is actually true
# otherwise move to more complex algorithm
assert same_path(ico0, ico1)
# aggregate timing info as (ic0, ic1) into ico0
ico0['t'] = (
ico0['t'],
ico1['t'],
)
yield ico0
rets += 1
print(' load %s: %u aggregated lines' % (fn, rets))
def load_speed_json(f):
j = json.load(f)
# Index speed indexes to names
speed_i2s = {}
for k, v in j['speed_model'].items():
i = v['speed_index']
if i != SI_NONE:
speed_i2s[i] = k
return j, speed_i2s

View File

@ -1,167 +0,0 @@
#!/usr/bin/env python3
from timfuz import Benchmark, A_di2ds, sw_ei_vals2s, sw_eo_vals2s, sw_i_vals2s
from timing_txt2json import gen_timing4a, load_speed_json
import glob
import math
import json
import sys
from collections import OrderedDict
def gen_diffs(speed_json_f, fns_in):
print('Loading data')
_speedj, speed_i2s = load_speed_json(speed_json_f)
for fn_in in fns_in:
for val in gen_timing4a(fn_in, speed_i2s):
# diff to get site only delay
tsites = {}
for k in val['t'][0].keys():
v = val['t'][0][k] - val['t'][1][k]
assert v >= 0
tsites[k] = v
yield val, tsites
# XXX: move to json converter?
def sd_parts(sd):
'''Return site_type, site_pin, bel_type, bel_pin as non-prefixed strings'''
# IOB_X0Y106 IOB_X0Y106/INBUF_EN IOB_X0Y106/INBUF_EN/OUT
# print(sd['site'], sd['bel'], sd['bel_pin'])
site_type = sd['site_type']
site, bel_type, bel_pin = sd['bel_pin'].split('/')
assert sd['site'] == site
assert sd['bel'] == site + '/' + bel_type
site_pin_str = sd['site_pin']
if site_pin_str:
site, site_pin = sd['site_pin'].split('/')
assert sd['site_pin'] == sd['site'] + '/' + site_pin
else:
site_pin = None
return site_type, site_pin, bel_type, bel_pin
def run(speed_json_f, fout, fns_in, verbose=0, corner=None):
'''
instead of writing to a simplified csv, lets just go directly to a delay format identical to what fabric uses
Path types:
-inter site: think these are removed for now?
1 model
NOTE: be careful of a net that goes external and comes back in, which isn't inter site
definition is that it doesn't have any site pins
-intra site
2 models
'''
fout.write(
'ico,fast_max fast_min slow_max slow_min,src_site_type,src_site,src_bel,src_bel_pin,dst_site_type,dst_site,dst_bel,dst_bel_pin\n'
)
for val, tsites in gen_diffs(speed_json_f, fns_in):
def mkb(t):
return (t['fast_max'], t['fast_min'], t['slow_max'], t['slow_min'])
bstr = ' '.join([str(x) for x in mkb(tsites)])
# Identify inter site transaction (SITEI)
if not val['src']['site_pin'] and not val['dst']['site_pin']:
# add one delay model for the path
# XXX: can these be solved exactly?
# might still have fanout and such
src_site_type, _src_site_pin, src_bel_type, src_bel_pin = sd_parts(
val['src'])
dst_site_type, _dst_site_pin, dst_bel_type, dst_bel_pin = sd_parts(
val['dst'])
assert src_site_type == dst_site_type
assert (src_bel_type, src_bel_pin) != (dst_bel_type, dst_bel_pin)
k = sw_i_vals2s(
src_site_type, src_bel_type, src_bel_pin, dst_bel_type,
dst_bel_pin)
row_ds = {k: 1}
elif val['src']['site_pin'] and val['dst']['site_pin']:
# if it exits a site it should enter another (possibly the same site)
# site in (SITEI) or site out (SITEO)?
# nah, keep things simple and just call them SITEW
row_ds = {}
def add_dst_delay():
sd = val['dst']
site_type, src_site_pin, dst_bel, dst_bel_pin = sd_parts(sd)
k = sw_ei_vals2s(site_type, src_site_pin, dst_bel, dst_bel_pin)
assert k not in row_ds
row_ds[k] = 1
def add_src_delay():
sd = val['src']
site_type, dst_site_pin, src_bel, src_bel_pin = sd_parts(sd)
k = sw_eo_vals2s(site_type, src_bel, src_bel_pin, dst_site_pin)
assert k not in row_ds
row_ds[k] = 1
add_dst_delay()
add_src_delay()
else:
# dropped by the tcl script
raise Exception("FIXME: handle destination but no source")
row_ico = 0
items = [str(row_ico), bstr]
for k, v in sorted(row_ds.items()):
items.append('%u %s' % (v, k))
fout.write(','.join(items) + '\n')
print('done')
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Convert obscure timing4.txt into timing4s.csv (site delay variable occurances)'
)
parser.add_argument('--verbose', type=int, help='')
# made a bulk conversion easier...keep?
parser.add_argument(
'--auto-name', action='store_true', help='timing4.txt => timing4i.csv')
parser.add_argument(
'--speed-json',
default='build_speed/speed.json',
help='Provides speed index to name translation')
parser.add_argument('--out', default=None, help='Output timing4i.csv file')
parser.add_argument('fns_in', nargs='+', help='Input timing4.txt files')
args = parser.parse_args()
bench = Benchmark()
fnout = args.out
if fnout is None:
if args.auto_name:
assert len(args.fns_in) == 1
fnin = args.fns_in[0]
fnout = fnin.replace('.txt', 's.csv')
assert fnout != fnin, 'Expect .txt in'
else:
# practically there are too many stray prints to make this work as expected
assert 0, 'File name required'
fnout = '/dev/stdout'
print("Writing to %s" % fnout)
fout = open(fnout, 'w')
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing4.txt')
run(
speed_json_f=open(args.speed_json, 'r'),
fout=fout,
fns_in=fns_in,
verbose=args.verbose)
if __name__ == '__main__':
main()