mirror of https://github.com/openXC7/prjxray.git
commit
e77842bec9
|
|
@ -1,10 +0,0 @@
|
|||
.NOTPARALLEL:
|
||||
|
||||
SUBDIRS := $(patsubst %/,%, $(wildcard */))
|
||||
|
||||
.PHONY: $(SUBDIRS)
|
||||
|
||||
$(MAKECMDGOALS): $(SUBDIRS)
|
||||
|
||||
$(SUBDIRS):
|
||||
$(MAKE) -C $@ -f ../Makefile.database $(MAKECMDGOALS)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
.PHONY: all clean
|
||||
|
||||
all: tilegrid.json
|
||||
|
||||
# Small dance to say that there is a single recipe that is run once to generate
|
||||
# multiple files.
|
||||
tileconn.json tilegrid.json: fuzzers
|
||||
.INTERMEDIATE: fuzzers
|
||||
fuzzers: SHELL:=/bin/bash
|
||||
fuzzers: settings.sh
|
||||
source settings.sh && $(MAKE) -C ../../fuzzers all
|
||||
|
||||
clean:
|
||||
rm -f *.db tileconn.json tilegrid.json *.yaml
|
||||
rm -rf html
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/.Xil
|
||||
/design/
|
||||
/design.bit
|
||||
/design.bits
|
||||
/design.dcp
|
||||
/usage_statistics_webtalk.*
|
||||
/vivado*
|
||||
/specimen_*
|
||||
build_speed
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# for now hammering on just picorv32
|
||||
# consider instead aggregating multiple projects
|
||||
PRJ?=picorv32
|
||||
PRJN?=8
|
||||
|
||||
all: build/timgrid-v.json
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
cd speed && $(MAKE) clean
|
||||
cd timgrid && $(MAKE) clean
|
||||
cd projects/$(PRJ) && $(MAKE) clean
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
# 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
|
||||
|
||||
Milestone 1 (MVP)
|
||||
* DONE
|
||||
* Provide any process corner with at least some of the fabric
|
||||
|
||||
Milestone 2
|
||||
* Provide all four fabric corners
|
||||
* Simple makefile based flow
|
||||
* Cleanup/separate fabric input targets
|
||||
|
||||
Milestone 3
|
||||
* Create site delay model
|
||||
|
||||
Final
|
||||
* Investigate ZERO
|
||||
* Investigate virtual switchboxes
|
||||
* Compare our vs Xilinx output on random designs
|
||||
|
||||
|
||||
### 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 virtual switchbox 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.
|
||||
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
'''
|
||||
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)
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
#!/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", ico=True)
|
||||
|
||||
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='timing3.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_*/timing3.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()
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#!/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='timing3.csv => timing3c.csv')
|
||||
parser.add_argument('--out', default=None, help='Output csv')
|
||||
parser.add_argument('--corner', help='Output csv')
|
||||
parser.add_argument('fns_in', nargs='+', help='timing3.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('timing3.csv', 'timing3c.csv')
|
||||
assert fnout != fnin, 'Expect timing3.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_*/timing3.csv')
|
||||
|
||||
run(fout=fout, fns_in=fns_in, corner=args.corner, verbose=args.verbose)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#!/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], ico=True)
|
||||
|
||||
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='timing3.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_*/timing3.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()
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#!/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, ico=True)
|
||||
bounds = Ads2bounds(Ads, bs)
|
||||
zeros = set()
|
||||
nonzeros = set()
|
||||
|
||||
for bound_name, bound_bs in bounds.items():
|
||||
sub = sub_json['subs'].get(bound_name, None)
|
||||
if sub:
|
||||
# put entire delay into pivot
|
||||
pivot = sub_json['pivots'][bound_name]
|
||||
assert pivot not in zeros
|
||||
nonzeros.add(pivot)
|
||||
non_pivot = set(sub.keys() - set([pivot]))
|
||||
#for name in non_pivot:
|
||||
# assert name not in nonzeros, (pivot, name, nonzeros)
|
||||
zeros.update(non_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 = 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 corner:
|
||||
zero_row = [None, None, None, None]
|
||||
zero_row[corner_s2i[corner]] = 0
|
||||
for zero in zeros - violations:
|
||||
yield zero, zero_row
|
||||
|
||||
|
||||
def run(fns_in, fnout, sub_json, corner=None, sort=False, verbose=False):
|
||||
'''
|
||||
if sort:
|
||||
sortf = sorted
|
||||
else:
|
||||
sortf = lambda x: x
|
||||
'''
|
||||
|
||||
with open(fnout, 'w') as fout:
|
||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
||||
#for name, corners in sortf(gen_flat(fnin, sub_json)):
|
||||
for name, corners in 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('--sort', action='store_true', help='')
|
||||
parser.add_argument('--sub-csv', help='')
|
||||
parser.add_argument(
|
||||
'--sub-json',
|
||||
required=True,
|
||||
help='Group substitutions to make fully ranked')
|
||||
parser.add_argument('--corner', default=None, help='')
|
||||
parser.add_argument('--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,
|
||||
#sort=args.sort,
|
||||
verbose=args.verbose,
|
||||
corner=args.corner)
|
||||
finally:
|
||||
print('Exiting after %s' % bench)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
specimen_*
|
||||
build
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
all:
|
||||
echo "FIXME: tie projects together"
|
||||
false
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Run corner specific calculations
|
||||
|
||||
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
|
||||
CORNER=slow_max
|
||||
ALLOW_ZERO_EQN?=N
|
||||
BADPRJ_OK?=N
|
||||
|
||||
all: build/$(CORNER)/timgrid-s.json
|
||||
|
||||
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
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
build/$(CORNER):
|
||||
mkdir build/$(CORNER)
|
||||
|
||||
build/checksub:
|
||||
false
|
||||
|
||||
build/$(CORNER)/leastsq.csv: build/sub.json build/grouped.csv build/checksub build/$(CORNER)
|
||||
# Create a rough timing model that approximately fits the given paths
|
||||
python3 $(TIMFUZ_DIR)/solve_leastsq.py --sub-json build/sub.json build/grouped.csv --corner $(CORNER) --out build/$(CORNER)/leastsq.csv.tmp
|
||||
mv build/$(CORNER)/leastsq.csv.tmp build/$(CORNER)/leastsq.csv
|
||||
|
||||
build/$(CORNER)/linprog.csv: build/$(CORNER)/leastsq.csv build/grouped.csv
|
||||
# Tweak rough timing model, making sure all constraints are satisfied
|
||||
ALLOW_ZERO_EQN=$(ALLOW_ZERO_EQN) python3 $(TIMFUZ_DIR)/solve_linprog.py --sub-json build/sub.json --sub-csv build/$(CORNER)/leastsq.csv --massage build/grouped.csv --corner $(CORNER) --out build/$(CORNER)/linprog.csv.tmp
|
||||
mv build/$(CORNER)/linprog.csv.tmp build/$(CORNER)/linprog.csv
|
||||
|
||||
build/$(CORNER)/flat.csv: build/$(CORNER)/linprog.csv
|
||||
# Take separated variables and back-annotate them to the original timing variables
|
||||
python3 $(TIMFUZ_DIR)/csv_group2flat.py --sub-json build/sub.json --corner $(CORNER) --out build/$(CORNER)/flat.csv.tmp build/$(CORNER)/linprog.csv
|
||||
mv build/$(CORNER)/flat.csv.tmp build/$(CORNER)/flat.csv
|
||||
|
||||
build/$(CORNER)/timgrid-s.json: build/$(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/$(CORNER)/timgrid-vc.json build/$(CORNER)/flat.csv
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
source ${XRAY_GENHEADER}
|
||||
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
|
||||
|
||||
timing_txt2csv () {
|
||||
python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing3.csv timing3.txt
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# so simple that likely no linprog adjustments will be needed
|
||||
ALLOW_ZERO_EQN?=Y
|
||||
include ../project.mk
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
Minimal, simple test
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
source ../generate.sh
|
||||
|
||||
vivado -mode batch -source ../generate.tcl
|
||||
timing_txt2csv
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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]
|
||||
set_property EXCLUDE_PLACEMENT 1 $roipb
|
||||
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_info3
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
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
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
include ../project.mk
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
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
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
source ../generate.sh
|
||||
|
||||
vivado -mode batch -source ../generate.tcl
|
||||
timing_txt2csv
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
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]
|
||||
set_property EXCLUDE_PLACEMENT 1 $roipb
|
||||
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_info3
|
||||
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
//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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
include ../project.mk
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
LUTs are physically laid out in an array and directly connected to a test pattern generator
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#!/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)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
source ${XRAY_GENHEADER}
|
||||
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
|
||||
|
||||
python ../generate.py --sdx 4 --sdy 4 >top.v
|
||||
vivado -mode batch -source ../generate.tcl
|
||||
python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing3.csv timing3.txt
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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]
|
||||
set_property EXCLUDE_PLACEMENT 1 $roipb
|
||||
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_info3
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
include ../project.mk
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
LUTs are physically laid out in an array and connected to test pattern generator and fed back to other LUTs
|
||||
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
#!/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)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
source ../generate.sh
|
||||
|
||||
python ../generate.py --sdx 4 --sdy 4 >top.v
|
||||
vivado -mode batch -source ../generate.tcl
|
||||
timing_txt2csv
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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]
|
||||
set_property EXCLUDE_PLACEMENT 1 $roipb
|
||||
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_info3
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
include ../project.mk
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
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.
|
||||
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
#!/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)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
source ../generate.sh
|
||||
|
||||
python ../generate.py --sdx 4 --sdy 4 >top.v
|
||||
vivado -mode batch -source ../generate.tcl
|
||||
timing_txt2csv
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
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]
|
||||
set_property EXCLUDE_PLACEMENT 1 $roipb
|
||||
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_info3
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# 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))
|
||||
CSVS := $(addsuffix /timing3.csv,$(SPECIMENS))
|
||||
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
|
||||
RREF_CORNER=slow_max
|
||||
ALLOW_ZERO_EQN?=N
|
||||
BADPRJ_OK?=N
|
||||
|
||||
TIMGRID_VCS=build/fast_max/timgrid-vc.json build/fast_min/timgrid-vc.json build/slow_max/timgrid-vc.json build/slow_min/timgrid-vc.json
|
||||
|
||||
all: build/timgrid-v.json
|
||||
|
||||
# make build/checksub first
|
||||
build/fast_max/timgrid-vc.json: build/checksub
|
||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_max
|
||||
build/fast_min/timgrid-vc.json: build/checksub
|
||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_min
|
||||
build/slow_max/timgrid-vc.json: build/checksub
|
||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_max
|
||||
build/slow_min/timgrid-vc.json: build/checksub
|
||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_min
|
||||
|
||||
$(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
|
||||
|
||||
.PHONY: all run clean
|
||||
|
||||
# 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
|
||||
|
||||
# rref should be the same regardless of corner
|
||||
build/sub.json: $(SPECIMENS_OK)
|
||||
mkdir -p build
|
||||
# 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) ; \
|
||||
python3 $(TIMFUZ_DIR)/rref.py --corner $(RREF_CORNER) --simplify --out build/sub.json.tmp $$csvs
|
||||
mv build/sub.json.tmp build/sub.json
|
||||
|
||||
build/grouped.csv: $(SPECIMENS_OK) build/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/sub.json --strict --out build/grouped.csv.tmp $$csvs
|
||||
mv build/grouped.csv.tmp build/grouped.csv
|
||||
|
||||
build/checksub: build/grouped.csv build/sub.json
|
||||
# Verify sub.json makes a cleanly solvable solution with no non-pivot leftover
|
||||
python3 $(TIMFUZ_DIR)/checksub.py --sub-json build/sub.json build/grouped.csv
|
||||
touch build/checksub
|
||||
|
||||
build/timgrid-v.json: $(TIMGRID_VCS)
|
||||
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out build/timgrid-v.json $(TIMGRID_VCS)
|
||||
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
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
|
||||
}
|
||||
|
||||
# Changed to group wires and nodes
|
||||
# This allows tracing the full path along with pips
|
||||
proc write_info3 {} {
|
||||
set outdir "."
|
||||
set fp [open "$outdir/timing3.txt" w]
|
||||
# bel as site/bel, so don't bother with site
|
||||
puts $fp "net src_bel dst_bel ico fast_max fast_min slow_max slow_min pips inodes wires"
|
||||
|
||||
set TIME_start [clock clicks -milliseconds]
|
||||
set verbose 0
|
||||
set equations 0
|
||||
set site_src_nets 0
|
||||
set site_dst_nets 0
|
||||
set neti 0
|
||||
set nets [get_nets -hierarchical]
|
||||
set nnets [llength $nets]
|
||||
foreach net $nets {
|
||||
incr neti
|
||||
#if {$neti >= 10} {
|
||||
# puts "Debug break"
|
||||
# break
|
||||
#}
|
||||
|
||||
puts "Net $neti / $nnets: $net"
|
||||
# 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_pin [get_pins -leaf -filter {DIRECTION == OUT} -of_objects $net]
|
||||
set src_bel [pin_bel $src_pin]
|
||||
set src_site [get_sites -of_objects $src_bel]
|
||||
# Only one net driver
|
||||
set src_site_pins [get_site_pins -filter {DIRECTION == OUT} -of_objects $net]
|
||||
|
||||
# Sometimes this is empty for reasons that escape me
|
||||
# Emitting direction doesn't help
|
||||
if {[llength $src_site_pins] < 1} {
|
||||
if $verbose {
|
||||
puts " Ignoring site internal net"
|
||||
}
|
||||
incr site_src_nets
|
||||
continue
|
||||
}
|
||||
set dst_site_pins_net [get_site_pins -filter {DIRECTION == IN} -of_objects $net]
|
||||
if {[llength $dst_site_pins_net] < 1} {
|
||||
puts " Skipping site internal source net"
|
||||
incr site_dst_nets
|
||||
continue
|
||||
}
|
||||
foreach src_site_pin $src_site_pins {
|
||||
if $verbose {
|
||||
puts "Source: $src_pin at site $src_site:$src_bel, spin $src_site_pin"
|
||||
}
|
||||
|
||||
# Run with and without interconnect only
|
||||
foreach ico "0 1" {
|
||||
set ico_flag ""
|
||||
if $ico {
|
||||
set ico_flag "-interconnect_only"
|
||||
set delays [get_net_delays $ico_flag -of_objects $net]
|
||||
} else {
|
||||
set delays [get_net_delays -of_objects $net]
|
||||
}
|
||||
foreach delay $delays {
|
||||
set delaystr [get_property NAME $delay]
|
||||
set dst_pins [get_property TO_PIN $delay]
|
||||
set dst_pin [get_pins $dst_pins]
|
||||
#puts " $delaystr: $src_pin => $dst_pin"
|
||||
set dst_bel [pin_bel $dst_pin]
|
||||
set dst_site [get_sites -of_objects $dst_bel]
|
||||
if $verbose {
|
||||
puts " Dest: $dst_pin at site $dst_site:$dst_bel"
|
||||
}
|
||||
|
||||
set dst_site_pins [get_site_pins -of_objects $dst_pin]
|
||||
# Some nets are internal
|
||||
# But should this happen on dest if we've already filtered source?
|
||||
if {"$dst_site_pins" eq ""} {
|
||||
continue
|
||||
}
|
||||
# Also apparantly you can have multiple of these as well
|
||||
foreach dst_site_pin $dst_site_pins {
|
||||
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]
|
||||
|
||||
# Want:
|
||||
# Site / BEL at src
|
||||
# Site / BEL at dst
|
||||
# Pips in between
|
||||
# Walk net, looking for interesting elements in between
|
||||
set pips [get_pips -of_objects $net -from $src_site_pin -to $dst_site_pin]
|
||||
if $verbose {
|
||||
foreach pip $pips {
|
||||
puts " PIP $pip"
|
||||
}
|
||||
}
|
||||
set nodes [get_nodes -of_objects $net -from $src_site_pin -to $dst_site_pin]
|
||||
#set wires [get_wires -of_objects $net -from $src_site_pin -to $dst_site_pin]
|
||||
set wires [get_wires -of_objects $nodes]
|
||||
|
||||
# puts $fp "$net $src_bel $dst_bel $ico $fast_max $fast_min $slow_max $slow_min $pips"
|
||||
puts -nonewline $fp "$net $src_bel $dst_bel $ico $fast_max $fast_min $slow_max $slow_min"
|
||||
|
||||
# Write pips w/ speed index
|
||||
puts -nonewline $fp " "
|
||||
set needspace 0
|
||||
foreach pip $pips {
|
||||
if $needspace {
|
||||
puts -nonewline $fp "|"
|
||||
}
|
||||
set speed_index [get_property SPEED_INDEX $pip]
|
||||
puts -nonewline $fp "$pip:$speed_index"
|
||||
set needspace 1
|
||||
}
|
||||
|
||||
# Write nodes
|
||||
#set nodes_str [string map {" " "|"} $nodes]
|
||||
#puts -nonewline $fp " $nodes_str"
|
||||
puts -nonewline $fp " "
|
||||
set needspace 0
|
||||
foreach node $nodes {
|
||||
if $needspace {
|
||||
puts -nonewline $fp "|"
|
||||
}
|
||||
set nwires [llength [get_wires -of_objects $node]]
|
||||
puts -nonewline $fp "$node:$nwires"
|
||||
set needspace 1
|
||||
}
|
||||
|
||||
# Write wires
|
||||
puts -nonewline $fp " "
|
||||
set needspace 0
|
||||
foreach wire $wires {
|
||||
if $needspace {
|
||||
puts -nonewline $fp "|"
|
||||
}
|
||||
set speed_index [get_property SPEED_INDEX $wire]
|
||||
puts -nonewline $fp "$wire:$speed_index"
|
||||
set needspace 1
|
||||
}
|
||||
|
||||
puts $fp ""
|
||||
|
||||
incr equations
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close $fp
|
||||
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
|
||||
puts "Took ms: $TIME_taken"
|
||||
puts "Generated $equations equations"
|
||||
puts "Skipped $site_src_nets (+ $site_dst_nets) site nets"
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
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
|
||||
|
||||
|
||||
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, drop_names=[]):
|
||||
self.Ads = Ads
|
||||
self.names = index_names(self.Ads)
|
||||
|
||||
# known zero delay elements
|
||||
self.drop_names = set(drop_names)
|
||||
# active names in rows
|
||||
# includes sub variables, excludes variables that have been substituted out
|
||||
self.base_names = set(self.names)
|
||||
self.names = set(self.base_names)
|
||||
# List of variable substitutions
|
||||
# k => dict of v:n entries that it came from
|
||||
self.subs = {}
|
||||
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.drop_names)))
|
||||
print(" Cols (preprocessed): %u" % len(self.base_names))
|
||||
print(" Drop names: %u" % len(self.drop_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):
|
||||
Ads, b = loadc_Ads_b(fn_ins, corner=corner, ico=True)
|
||||
if simplify:
|
||||
print('Simplifying corner %s' % (corner, ))
|
||||
Ads, b = simplify_rows(Ads, b, remove_zd=False, corner=corner)
|
||||
return State(Ads)
|
||||
|
||||
|
||||
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 Anp2matrix(Anp):
|
||||
'''
|
||||
Original idea was to make into a square matrix
|
||||
but this loses too much information
|
||||
so now this actually isn't doing anything and should probably be eliminated
|
||||
'''
|
||||
|
||||
ncols = len(Anp[0])
|
||||
A_ub2 = [np.zeros(ncols) for _i in range(ncols)]
|
||||
dst_rowi = 0
|
||||
for rownp in Anp:
|
||||
A_ub2[dst_rowi] = np.add(A_ub2[dst_rowi], rownp)
|
||||
dst_rowi = (dst_rowi + 1) % ncols
|
||||
return A_ub2
|
||||
|
||||
|
||||
def row_np2ds(rownp, names):
|
||||
ret = {}
|
||||
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 = {}
|
||||
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])))
|
||||
if 0:
|
||||
print('Combining rows into matrix')
|
||||
mnp = Anp2matrix(Anp)
|
||||
else:
|
||||
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 = {}
|
||||
|
||||
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(set(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, verbose=0):
|
||||
print('Loading data')
|
||||
|
||||
assert len(fn_ins) > 0
|
||||
state = State.load(fn_ins, simplify=simplify, corner=corner)
|
||||
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(
|
||||
'--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='timing3.csv input files')
|
||||
args = parser.parse_args()
|
||||
bench = Benchmark()
|
||||
|
||||
fns_in = args.fns_in
|
||||
if not fns_in:
|
||||
fns_in = glob.glob('specimen_*/timing3.csv')
|
||||
|
||||
try:
|
||||
run(
|
||||
fnout=args.out,
|
||||
fn_ins=fns_in,
|
||||
simplify=args.simplify,
|
||||
corner=args.corner,
|
||||
verbose=args.verbose)
|
||||
finally:
|
||||
print('Exiting after %s' % bench)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from timfuz import Benchmark, load_sub, corner_s2i, acorner2csv
|
||||
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):
|
||||
if val:
|
||||
# Scale by number occurances
|
||||
ub = row_b / val
|
||||
if ub >= 0:
|
||||
x0[coli] = min(x0[coli], ub)
|
||||
return x0
|
||||
|
||||
|
||||
def save(outfn, xvals, names, corner):
|
||||
# ballpark minimum actual observed delay is around 7 (carry chain)
|
||||
# anything less than one is probably a solver artifact
|
||||
delta = 0.5
|
||||
corneri = corner_s2i[corner]
|
||||
|
||||
# Round conservatively
|
||||
roundf = {
|
||||
'fast_max': math.ceil,
|
||||
'fast_min': math.floor,
|
||||
'slow_max': math.ceil,
|
||||
'slow_min': math.floor,
|
||||
}[corner]
|
||||
|
||||
print('Writing results')
|
||||
skips = 0
|
||||
keeps = 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
|
||||
|
||||
# also review ceil vs floor choice for min vs max
|
||||
# lets be more conservative for now
|
||||
if xval < delta:
|
||||
#print('Skipping %s: %0.6f' % (name, xval))
|
||||
skips += 1
|
||||
continue
|
||||
keeps += 1
|
||||
#xvali = round(xval)
|
||||
|
||||
items = [str(row_ico), acorner2csv(roundf(xval), corneri)]
|
||||
items.append('%u %s' % (1, name))
|
||||
fout.write(','.join(items) + '\n')
|
||||
print(
|
||||
'Wrote: skip %u => %u / %u valid delays' % (skips, keeps, len(names)))
|
||||
assert keeps, 'Failed to estimate delay'
|
||||
|
||||
|
||||
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:
|
||||
save(outfn, res.x, names, corner)
|
||||
|
||||
|
||||
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='timing3.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_*/timing3.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()
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
#!/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 save(outfn, xvals, names, corner):
|
||||
# ballpark minimum actual observed delay is around 7 (carry chain)
|
||||
# anything less than one is probably a solver artifact
|
||||
delta = 0.5
|
||||
corneri = corner_s2i[corner]
|
||||
|
||||
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
|
||||
|
||||
# FIXME: only report for the given corner?
|
||||
# also review ceil vs floor choice for min vs max
|
||||
# lets be more conservative for now
|
||||
if xval < delta:
|
||||
print('WARNING: near 0 delay on %s: %0.6f' % (name, xval))
|
||||
zeros += 1
|
||||
#continue
|
||||
items = [str(row_ico), acorner2csv(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))
|
||||
|
||||
|
||||
def run_corner(
|
||||
Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None):
|
||||
if len(Anp) == 0:
|
||||
print('WARNING: zero equations')
|
||||
if outfn:
|
||||
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] = kwargs['nit']
|
||||
if time.time() - tlast[0] > 1.0:
|
||||
sys.stdout.write('I:%d ' % kwargs['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:
|
||||
save(outfn, res.x, names, corner)
|
||||
|
||||
|
||||
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('--sub-csv', help='')
|
||||
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='timing3.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_*/timing3.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,
|
||||
sub_csv=args.sub_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()
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
build
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
all: build/speed.json
|
||||
|
||||
build/node.txt: speed_json.py generate.tcl
|
||||
mkdir -p build
|
||||
cd build && vivado -mode batch -source ../generate.tcl
|
||||
|
||||
build/speed.json: build/node.txt
|
||||
cd build && python ../speed_json.py speed_model.txt node.txt speed.json
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
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.
|
||||
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
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
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
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)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
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
|
|
@ -0,0 +1,89 @@
|
|||
#!/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()
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import timfuz
|
||||
from timfuz import loadc_Ads_bs, Ads2bounds
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
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], ico=True)
|
||||
bounds = Ads2bounds(Ads, bs)
|
||||
|
||||
for tile in tilej['tiles'].values():
|
||||
pips = tile['pips']
|
||||
for k, v in pips.items():
|
||||
pips[k] = bounds.get('PIP_' + v, [None, None, None, None])
|
||||
|
||||
wires = tile['wires']
|
||||
for k, v in wires.items():
|
||||
wires[k] = bounds.get('WIRE_' + v, [None, None, None, None])
|
||||
|
||||
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.json)')
|
||||
args = parser.parse_args()
|
||||
|
||||
run(args.fn_ins, args.out, args.timgrid_s, verbose=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,849 @@
|
|||
#!/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
|
||||
|
||||
from benchmark import Benchmark
|
||||
|
||||
NAME_ZERO = set(
|
||||
[
|
||||
"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),
|
||||
])
|
||||
|
||||
|
||||
# Equations are filtered out until nothing is left
|
||||
class SimplifiedToZero(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def allow_zero_eqns():
|
||||
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 = [(dict(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))
|
||||
zero_ds = 0
|
||||
zero_es = 0
|
||||
for loopi, (b, rowd) in enumerate(zip(b_ub, Ads)):
|
||||
if loopi % progress == 0:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
|
||||
# TODO: elements have zero delay (ex: COUT)
|
||||
# Remove these from now since they make me nervous
|
||||
# Although they should just solve to 0
|
||||
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
|
||||
|
||||
rowt = Ar_ds2t(rowd)
|
||||
eqns[rowt] = minmax(eqns.get(rowt, T_UNK), b)
|
||||
|
||||
print(' done')
|
||||
|
||||
print(
|
||||
'Simplify rows: %d => %d rows w/ zd %d, 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)
|
||||
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 derive_eq_by_row(A_ubd, b_ub, verbose=0, col_lim=0, tweak=False):
|
||||
'''
|
||||
Derive equations by subtracting whole rows
|
||||
|
||||
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
|
||||
'''
|
||||
rows = len(A_ubd)
|
||||
assert rows == len(b_ub)
|
||||
|
||||
# Index equations into hash maps so can lookup sparse elements quicker
|
||||
assert len(A_ubd) == len(b_ub)
|
||||
A_ubd_ret = copy.copy(A_ubd)
|
||||
assert len(A_ubd) == len(A_ubd_ret)
|
||||
|
||||
#print('Finding subsets')
|
||||
ltes = 0
|
||||
scs = 0
|
||||
b_ub_ret = list(b_ub)
|
||||
sys.stdout.write('Deriving rows ')
|
||||
sys.stdout.flush()
|
||||
progress = max(1, rows / 100)
|
||||
for row_refi, row_ref in enumerate(A_ubd):
|
||||
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(A_ubd):
|
||||
if row_refi == row_cmpi or col_lim and len(row_cmp) > col_lim:
|
||||
continue
|
||||
# FIXME: this check was supposed to be removed
|
||||
'''
|
||||
Every elements in row_cmp is in row_ref
|
||||
but this doesn't mean the constants are smaller
|
||||
Filter these out
|
||||
'''
|
||||
# XXX: just reduce and filter out solutions with positive constants
|
||||
# or actually are these also useful as is?
|
||||
lte = lte_const(row_ref, row_cmp)
|
||||
if lte:
|
||||
ltes += 1
|
||||
sc = 0 and shared_const(row_ref, row_cmp)
|
||||
if sc:
|
||||
scs += 1
|
||||
if lte or sc:
|
||||
if verbose:
|
||||
print('')
|
||||
print('match')
|
||||
print(' ', row_ref, b_ub[row_refi])
|
||||
print(' ', row_cmp, b_ub[row_cmpi])
|
||||
# Reduce
|
||||
A_new = reduce_const(row_ref, row_cmp)
|
||||
# Did this actually significantly reduce the search space?
|
||||
#if tweak and len(A_new) > 4 and len(A_new) > len(row_cmp) / 2:
|
||||
if tweak and len(A_new) > 8 and len(A_new) > len(row_cmp) / 2:
|
||||
continue
|
||||
b_new = b_ub[row_refi] - b_ub[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")
|
||||
continue
|
||||
if verbose:
|
||||
print('OK')
|
||||
A_ubd_ret.append(A_new)
|
||||
b_ub_ret.append(b_new)
|
||||
print(' done')
|
||||
|
||||
#A_ub_ret = A_di2np(A_ubd2, cols=cols)
|
||||
print(
|
||||
'Derive row: %d => %d rows using %d lte, %d sc' %
|
||||
(len(b_ub), len(b_ub_ret), ltes, scs))
|
||||
assert len(A_ubd_ret) == len(b_ub_ret)
|
||||
return A_ubd_ret, b_ub_ret
|
||||
|
||||
|
||||
def derive_eq_by_col(A_ubd, b_ub, verbose=0):
|
||||
'''
|
||||
Derive equations by subtracting out all bounded constants (ie "known" columns)
|
||||
'''
|
||||
rows = len(A_ubd)
|
||||
|
||||
# Find all entries where
|
||||
|
||||
# Index equations with a single constraint
|
||||
knowns = {}
|
||||
sys.stdout.write('Derive col indexing ')
|
||||
#A_ubd = A_ub_np2d(A_ub)
|
||||
sys.stdout.flush()
|
||||
progress = max(1, rows / 100)
|
||||
for row_refi, row_refd in enumerate(A_ubd):
|
||||
if row_refi % progress == 0:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
if len(row_refd) == 1:
|
||||
k, v = list(row_refd.items())[0]
|
||||
# Reduce any constants to canonical form
|
||||
if v != 1:
|
||||
row_refd[k] = 1
|
||||
b_ub[row_refi] /= v
|
||||
knowns[k] = b_ub[row_refi]
|
||||
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_ub_ret = []
|
||||
A_ubd_ret = []
|
||||
sys.stdout.write('Derive col main ')
|
||||
sys.stdout.flush()
|
||||
progress = max(1, rows / 100)
|
||||
for row_refi, row_refd in enumerate(A_ubd):
|
||||
if row_refi % progress == 0:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
# Reduce as much as possible
|
||||
#row_new = {}
|
||||
row_new = OrderedDict()
|
||||
b_new = b_ub[row_refi]
|
||||
# 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
|
||||
if b_new <= 0:
|
||||
continue
|
||||
|
||||
A_ubd_ret.append(row_new)
|
||||
b_ub_ret.append(b_new)
|
||||
print(' done')
|
||||
|
||||
print('Derive col: %d => %d rows' % (len(b_ub), len(b_ub_ret)))
|
||||
return A_ubd_ret, b_ub_ret
|
||||
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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 = set()
|
||||
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['drop_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 = set()
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
for corner, corneri in corner_s2i.items():
|
||||
print('Corner %s' % corner)
|
||||
for etype in ('pips', 'wires'):
|
||||
net = stats[etype]['net']
|
||||
solved = stats[etype]['solved'][corneri]
|
||||
covered = stats[etype]['covered'][corneri]
|
||||
print(
|
||||
' %s: %u / %u solved, %u / %u covered' %
|
||||
(etype, solved, net, covered, net))
|
||||
|
|
@ -0,0 +1,422 @@
|
|||
#!/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 shared_const(row_ref, row_cmp):
|
||||
'''Return true if more constants are equal than not equal'''
|
||||
#return False
|
||||
matches = 0
|
||||
unmatches = 0
|
||||
ks = list(row_ref.keys()) + list(row_cmp.keys())
|
||||
for k in ks:
|
||||
vr = row_ref.get(k, None)
|
||||
vc = row_cmp.get(k, None)
|
||||
# At least one
|
||||
if vr is not None and vc is not None:
|
||||
if vc == vr:
|
||||
matches += 1
|
||||
else:
|
||||
unmatches += 1
|
||||
else:
|
||||
unmatches += 1
|
||||
|
||||
# Will equation reduce if subtracted?
|
||||
return matches > unmatches
|
||||
|
||||
|
||||
def reduce_const(row_ref, row_cmp):
|
||||
'''Subtract cmp constants from ref'''
|
||||
#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, tweak=False):
|
||||
'''
|
||||
Derive equations by subtracting whole rows
|
||||
|
||||
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
|
||||
scs = 0
|
||||
b_ret = list(b)
|
||||
sys.stdout.write('Deriving rows (%u) ' % rows)
|
||||
sys.stdout.flush()
|
||||
progress = int(max(1, rows / 100))
|
||||
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
|
||||
# FIXME: this check was supposed to be removed
|
||||
'''
|
||||
Every elements in row_cmp is in row_ref
|
||||
but this doesn't mean the constants are smaller
|
||||
Filter these out
|
||||
'''
|
||||
# XXX: just reduce and filter out solutions with positive constants
|
||||
# or actually are these also useful as is?
|
||||
lte = lte_const(row_ref, row_cmp)
|
||||
if lte:
|
||||
ltes += 1
|
||||
sc = 0 and shared_const(row_ref, row_cmp)
|
||||
if sc:
|
||||
scs += 1
|
||||
if lte or sc:
|
||||
if verbose:
|
||||
print('')
|
||||
print('match')
|
||||
print(' ', row_ref, b[row_refi])
|
||||
print(' ', row_cmp, b[row_cmpi])
|
||||
# Reduce
|
||||
A_new = reduce_const(row_ref, row_cmp)
|
||||
# Did this actually significantly reduce the search space?
|
||||
#if tweak and len(A_new) > 4 and len(A_new) > len(row_cmp) / 2:
|
||||
if tweak and len(A_new) > 8 and len(A_new) > len(row_cmp) / 2:
|
||||
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")
|
||||
continue
|
||||
if verbose:
|
||||
print('OK')
|
||||
Ads_ret.append(A_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, %d sc' %
|
||||
(len(b), len(b_ret), ltes, scs))
|
||||
assert len(Ads_ret) == len(b_ret)
|
||||
return Ads_ret, b_ret
|
||||
|
||||
|
||||
def derive_eq_by_near_row(Ads, b, verbose=0, col_lim=0, tweak=False):
|
||||
'''
|
||||
Derive equations by subtracting whole rows
|
||||
|
||||
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
|
||||
'''
|
||||
rows = len(Ads)
|
||||
assert rows == len(b)
|
||||
rowdelta = int(rows / 2)
|
||||
|
||||
# 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
|
||||
scs = 0
|
||||
b_ret = list(b)
|
||||
sys.stdout.write('Deriving rows (%u) ' % rows)
|
||||
sys.stdout.flush()
|
||||
progress = int(max(1, rows / 100))
|
||||
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):
|
||||
for row_cmpi in range(max(0, row_refi - rowdelta),
|
||||
min(len(Ads), row_refi + rowdelta)):
|
||||
if row_refi == row_cmpi or col_lim and len(row_cmp) > col_lim:
|
||||
continue
|
||||
row_cmp = Ads[row_cmpi]
|
||||
# FIXME: this check was supposed to be removed
|
||||
'''
|
||||
Every elements in row_cmp is in row_ref
|
||||
but this doesn't mean the constants are smaller
|
||||
Filter these out
|
||||
'''
|
||||
# XXX: just reduce and filter out solutions with positive constants
|
||||
# or actually are these also useful as is?
|
||||
lte = lte_const(row_ref, row_cmp)
|
||||
if lte:
|
||||
ltes += 1
|
||||
sc = 0 and shared_const(row_ref, row_cmp)
|
||||
if sc:
|
||||
scs += 1
|
||||
if lte or sc:
|
||||
if verbose:
|
||||
print('')
|
||||
print('match')
|
||||
print(' ', row_ref, b[row_refi])
|
||||
print(' ', row_cmp, b[row_cmpi])
|
||||
# Reduce
|
||||
A_new = reduce_const(row_ref, row_cmp)
|
||||
# Did this actually significantly reduce the search space?
|
||||
#if tweak and len(A_new) > 4 and len(A_new) > len(row_cmp) / 2:
|
||||
#if tweak and len(A_new) > 8 and len(A_new) > len(row_cmp) / 2:
|
||||
# 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")
|
||||
continue
|
||||
if verbose:
|
||||
print('OK')
|
||||
Ads_ret.append(A_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, %d sc' %
|
||||
(len(b), len(b_ret), ltes, scs))
|
||||
assert len(Ads_ret) == len(b_ret)
|
||||
return Ads_ret, b_ret
|
||||
|
||||
|
||||
def derive_eq_by_col(Ads, b_ub, verbose=0):
|
||||
'''
|
||||
Derive equations by subtracting out all bounded constants (ie "known" columns)
|
||||
'''
|
||||
rows = len(Ads)
|
||||
|
||||
# Find all entries where
|
||||
|
||||
# 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]
|
||||
# Reduce any constants to canonical form
|
||||
if v != 1:
|
||||
row_refd[k] = 1
|
||||
b_ub[row_refi] /= v
|
||||
knowns[k] = b_ub[row_refi]
|
||||
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()
|
||||
# Reduce as much as possible
|
||||
#row_new = {}
|
||||
row_new = OrderedDict()
|
||||
b_new = b_ub[row_refi]
|
||||
# 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
|
||||
if b_new <= 0:
|
||||
continue
|
||||
|
||||
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):
|
||||
'''
|
||||
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 debug(what):
|
||||
if 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)
|
||||
col_lim = 15
|
||||
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, tweak=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")
|
||||
|
||||
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)
|
||||
# possible that a new equation was generated and taken away, but close enough
|
||||
if n_orig == len(b) and col_lim >= cols:
|
||||
break
|
||||
col_lim += col_lim / 5
|
||||
di += 1
|
||||
|
||||
dend = len(b)
|
||||
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))
|
||||
return Ads, b
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
#!/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
|
||||
from timfuz_massage import massage_equations
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
|
||||
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'''
|
||||
#assert len(bounds) > 0
|
||||
|
||||
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 = []
|
||||
for row_ds, row_b in zip(Ads, b):
|
||||
# some variables get estimated at 0
|
||||
est = sum([bounds.get(k, T_UNK) * 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)
|
||||
return ret_Ads, ret_b
|
||||
|
||||
|
||||
def run(
|
||||
fns_in,
|
||||
corner,
|
||||
run_corner,
|
||||
sub_json=None,
|
||||
sub_csv=None,
|
||||
dedup=True,
|
||||
massage=False,
|
||||
outfn=None,
|
||||
verbose=False,
|
||||
**kwargs):
|
||||
print('Loading data')
|
||||
Ads, b = loadc_Ads_b(fns_in, corner, ico=True)
|
||||
|
||||
# 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 sub_csv:
|
||||
Ads2, b2 = loadc_Ads_b([sub_csv], corner, ico=True)
|
||||
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)
|
||||
|
|
@ -0,0 +1 @@
|
|||
build
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
all: build/timgrid.json
|
||||
|
||||
build/timgrid.txt: generate.tcl
|
||||
mkdir -p build
|
||||
cd build && 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
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
source ${XRAY_GENHEADER}
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
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
|
||||
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
#!/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()
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//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
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
`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
|
||||
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
#!/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 build_tilejo(fnins):
|
||||
'''
|
||||
{
|
||||
"tiles": {
|
||||
"BRKH_B_TERM_INT": {
|
||||
"pips": {},
|
||||
"wires": {
|
||||
"B_TERM_UTURN_INT_ER1BEG0": [
|
||||
null,
|
||||
null,
|
||||
93,
|
||||
null
|
||||
],
|
||||
'''
|
||||
|
||||
tilejo = {"tiles": {}}
|
||||
for fnin in fnins:
|
||||
tileji = json.load(open(fnin, 'r'))
|
||||
for tilek, tilevi in tileji['tiles'].items():
|
||||
# No previous data? Copy
|
||||
tilevo = tilejo['tiles'].get(tilek, None)
|
||||
if tilevo is None:
|
||||
tilejo['tiles'][tilek] = tilevi
|
||||
# Otherwise combine
|
||||
else:
|
||||
|
||||
def process_type(etype):
|
||||
for pipk, pipvi in tilevi[etype].items():
|
||||
pipvo = tilevo[etype][pipk]
|
||||
for cornerk, corneri in corner_s2i.items():
|
||||
cornervo = pipvo[corneri]
|
||||
cornervi = pipvi[corneri]
|
||||
# no new data
|
||||
if cornervi is None:
|
||||
pass
|
||||
# no previous data
|
||||
elif cornervo is None:
|
||||
pipvo[corneri] = cornervi
|
||||
# combine
|
||||
else:
|
||||
minmax = corner2minmax[cornerk]
|
||||
pipvo[corneri] = minmax(cornervi, cornervo)
|
||||
|
||||
process_type('pips')
|
||||
process_type('wires')
|
||||
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 check_corners_minmax(tilej, verbose=False):
|
||||
# TODO: check fast vs slow
|
||||
pass
|
||||
|
||||
|
||||
def run(fnins, fnout, verbose=False):
|
||||
tilejo = build_tilejo(fnins)
|
||||
check_corner_minmax(tilejo)
|
||||
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()
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
#!/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
|
||||
|
||||
# prefix to make easier to track
|
||||
# models do not overlap between PIPs and WIREs
|
||||
PREFIX_W = 'WIRE_'
|
||||
PREFIX_P = 'PIP_'
|
||||
|
||||
|
||||
def parse_pip(s):
|
||||
# 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, int(speed_index)
|
||||
|
||||
|
||||
def parse_node(s):
|
||||
node, nwires = s.split(':')
|
||||
return node, int(nwires)
|
||||
|
||||
|
||||
def parse_wire(s):
|
||||
# CLBLM_R_X3Y80/CLBLM_M_D6:952
|
||||
wirestr, speed_index = s.split(':')
|
||||
site, instance = wirestr.split('/')
|
||||
return site, instance, int(speed_index)
|
||||
|
||||
|
||||
# FIXME: these actually have a delay element
|
||||
# Probably need to put these back in
|
||||
def remove_virtual_pips(pips):
|
||||
return pips
|
||||
return filter(lambda pip: not re.match(r'CLBL[LM]_[LR]_', pip[0]), pips)
|
||||
|
||||
|
||||
def load_timing3(f, name='file'):
|
||||
# src_bel dst_bel ico fast_max fast_min slow_max slow_min pips
|
||||
f.readline()
|
||||
ret = []
|
||||
bads = 0
|
||||
for l in f:
|
||||
# FIXME: hack
|
||||
if 0 and 'CLK' in l:
|
||||
continue
|
||||
|
||||
l = l.strip()
|
||||
if not l:
|
||||
continue
|
||||
parts = l.split(' ')
|
||||
# FIXME: deal with these nodes
|
||||
if len(parts) != 11:
|
||||
bads += 1
|
||||
continue
|
||||
net, src_bel, dst_bel, ico, fast_max, fast_min, slow_max, slow_min, pips, nodes, wires = parts
|
||||
pips = pips.split('|')
|
||||
nodes = nodes.split('|')
|
||||
wires = wires.split('|')
|
||||
ret.append(
|
||||
{
|
||||
'net': net,
|
||||
'src_bel': src_bel,
|
||||
'dst_bel': dst_bel,
|
||||
'ico': int(ico),
|
||||
# ps
|
||||
'fast_max': int(fast_max),
|
||||
'fast_min': int(fast_min),
|
||||
'slow_max': int(slow_max),
|
||||
'slow_min': int(slow_min),
|
||||
'pips': remove_virtual_pips([parse_pip(pip) for pip in pips]),
|
||||
'nodes': [parse_node(node) for node in nodes],
|
||||
'wires': [parse_wire(wire) for wire in wires],
|
||||
'line': l,
|
||||
})
|
||||
print(' load %s: %d bad, %d good' % (name, bads, len(ret)))
|
||||
#assert 0
|
||||
return ret
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 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 vals2Adi(vals, speed_i2s, name_tr={}, name_drop=[], verbose=False):
|
||||
def pip2speed(pip):
|
||||
_site, _name, speed_index = pip
|
||||
return PREFIX_P + speed_i2s[speed_index]
|
||||
|
||||
def wire2speed(wire):
|
||||
_site, _name, speed_index = wire
|
||||
return PREFIX_W + speed_i2s[speed_index]
|
||||
|
||||
# Want this ordered
|
||||
names = OrderedDict()
|
||||
|
||||
print(
|
||||
'Creating matrix w/ tr: %d, drop: %d' % (len(name_tr), len(name_drop)))
|
||||
|
||||
# Take sites out entirely using handy "interconnect only" option
|
||||
#vals = filter(lambda x: str(x).find('SLICE') >= 0, vals)
|
||||
# Highest count while still getting valid result
|
||||
|
||||
# First index all of the given pip types
|
||||
# Start out as set then convert to list to keep matrix order consistent
|
||||
sys.stdout.write('Indexing delay elements ')
|
||||
sys.stdout.flush()
|
||||
progress = max(1, len(vals) / 100)
|
||||
for vali, val in enumerate(vals):
|
||||
if vali % progress == 0:
|
||||
sys.stdout.write('.')
|
||||
sys.stdout.flush()
|
||||
odl = [(pip2speed(pip), None) for pip in val['pips']]
|
||||
names.update(OrderedDict(odl))
|
||||
|
||||
odl = [(wire2speed(wire), None) for wire in val['wires']]
|
||||
names.update(OrderedDict(odl))
|
||||
print(' done')
|
||||
|
||||
# Apply transform
|
||||
orig_names = len(names)
|
||||
for k in (list(name_drop) + list(name_tr.keys())):
|
||||
if k in names:
|
||||
del names[k]
|
||||
else:
|
||||
print('WARNING: failed to remove %s' % k)
|
||||
names.update(OrderedDict([(name, None) for name in name_tr.values()]))
|
||||
print('Names tr %d => %d' % (orig_names, len(names)))
|
||||
|
||||
# Make unique list
|
||||
names = list(names.keys())
|
||||
name_s2i = {}
|
||||
for namei, name in enumerate(names):
|
||||
name_s2i[name] = namei
|
||||
if verbose:
|
||||
for name in names:
|
||||
print('NAME: ', name)
|
||||
for name in name_drop:
|
||||
print('DROP: ', name)
|
||||
for l, r in name_tr.items():
|
||||
print('TR: %s => %s' % (l, r))
|
||||
|
||||
# Now create a matrix with all of these delays
|
||||
# Each row needs len(names) elements
|
||||
# -2 means 2 elements present, 0 means absent
|
||||
# (could hit same pip twice)
|
||||
print('Creating delay element matrix w/ %d names' % len(names))
|
||||
Adi = [None for _i in range(len(vals))]
|
||||
for vali, val in enumerate(vals):
|
||||
|
||||
def add_name(name):
|
||||
if name in name_drop:
|
||||
return
|
||||
name = name_tr.get(name, name)
|
||||
namei = name_s2i[name]
|
||||
row_di[namei] = row_di.get(namei, 0) + 1
|
||||
|
||||
# Start with 0 occurances
|
||||
#row = [0 for _i in range(len(names))]
|
||||
row_di = {}
|
||||
|
||||
#print('pips: ', val['pips']
|
||||
for pip in val['pips']:
|
||||
add_name(pip2speed(pip))
|
||||
for wire in val['wires']:
|
||||
add_name(wire2speed(wire))
|
||||
#A_ub.append(row)
|
||||
Adi[vali] = row_di
|
||||
|
||||
return Adi, names
|
||||
|
||||
|
||||
# TODO: load directly as Ads
|
||||
# remove names_tr, names_drop
|
||||
def vals2Ads(vals, speed_i2s, verbose=False):
|
||||
Adi, names = vals2Adi(vals, speed_i2s, verbose=False)
|
||||
return A_di2ds(Adi, names)
|
||||
|
||||
|
||||
def load_Ads(speed_json_f, f_ins):
|
||||
|
||||
print('Loading data')
|
||||
|
||||
_speedj, speed_i2s = load_speed_json(speed_json_f)
|
||||
|
||||
vals = []
|
||||
for avals in [load_timing3(f_in, name) for f_in, name in f_ins]:
|
||||
vals.extend(avals)
|
||||
|
||||
Ads = vals2Ads(vals, speed_i2s)
|
||||
|
||||
def mkb(val):
|
||||
return (
|
||||
val['fast_max'], val['fast_min'], val['slow_max'], val['slow_min'])
|
||||
|
||||
b = [mkb(val) for val in vals]
|
||||
ico = [val['ico'] for val in vals]
|
||||
|
||||
return Ads, b, ico
|
||||
|
||||
|
||||
def run(speed_json_f, fout, f_ins, verbose=0, corner=None):
|
||||
Ads, bs, ico = load_Ads(speed_json_f, f_ins)
|
||||
|
||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
||||
for row_bs, row_ds, row_ico in zip(bs, Ads, ico):
|
||||
# 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 timing3.txt into more readable but roughly equivilent timing3.csv'
|
||||
)
|
||||
|
||||
parser.add_argument('--verbose', type=int, help='')
|
||||
# made a bulk conversion easier...keep?
|
||||
parser.add_argument(
|
||||
'--auto-name', action='store_true', help='timing3.txt => timing3.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 timing3.csv file')
|
||||
parser.add_argument('fns_in', nargs='+', help='Input timing3.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', '.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_*/timing3.txt')
|
||||
|
||||
run(
|
||||
speed_json_f=open(args.speed_json, 'r'),
|
||||
fout=fout,
|
||||
f_ins=[(open(fn_in, 'r'), fn_in) for fn_in in fns_in],
|
||||
verbose=args.verbose)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
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 % 1000 == 0:
|
||||
print 'Check node %d' % nodei
|
||||
# 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='/dev/stdin', nargs='?', help='Input file')
|
||||
args = parser.parse_args()
|
||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
#!/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()
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
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 % 1000 == 0:
|
||||
print 'Check node %d' % nodei
|
||||
# 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='/dev/stdin', nargs='?', help='Input file')
|
||||
args = parser.parse_args()
|
||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
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 % 1000 == 0:
|
||||
print 'Check node %d' % nodei
|
||||
# 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='/dev/stdin', nargs='?', help='Input file')
|
||||
args = parser.parse_args()
|
||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
||||
Loading…
Reference in New Issue