mirror of https://github.com/openXC7/prjxray.git
Merge pull request #897 from antmicro/clean-007
Fuzzers: 007-timings: remove unused code
This commit is contained in:
commit
0548c105e4
|
|
@ -1,32 +1,10 @@
|
||||||
# for now hammering on just picorv32
|
|
||||||
# consider instead aggregating multiple projects
|
|
||||||
PRJ?=oneblinkw
|
|
||||||
PRJN?=1
|
|
||||||
|
|
||||||
run: all
|
run: all
|
||||||
all: build/timgrid-v.json bel/build/sdf
|
all: bel/build/sdf
|
||||||
touch run.ok
|
touch run.ok
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
|
||||||
cd speed && $(MAKE) clean
|
|
||||||
cd timgrid && $(MAKE) clean
|
|
||||||
cd bel && $(MAKE) clean
|
cd bel && $(MAKE) clean
|
||||||
cd projects/$(PRJ) && $(MAKE) clean
|
|
||||||
|
|
||||||
bel/build/sdf:
|
bel/build/sdf:
|
||||||
cd bel && $(MAKE)
|
cd bel && $(MAKE)
|
||||||
|
|
||||||
speed/build/speed.json:
|
|
||||||
cd speed && $(MAKE)
|
|
||||||
|
|
||||||
timgrid/build/timgrid.json:
|
|
||||||
cd timgrid && $(MAKE)
|
|
||||||
|
|
||||||
build/timgrid-v.json: projects/$(PRJ)/build/timgrid-v.json
|
|
||||||
mkdir -p build
|
|
||||||
cp projects/$(PRJ)/build/timgrid-v.json build/timgrid-v.json
|
|
||||||
|
|
||||||
projects/$(PRJ)/build/timgrid-v.json: speed/build/speed.json timgrid/build/timgrid.json
|
|
||||||
cd projects/$(PRJ) && $(MAKE) N=$(PRJN)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
# Timing analysis fuzzer (timfuz)
|
|
||||||
|
|
||||||
WIP: 2018-09-10: this process is just starting together and is going to get significant cleanup. But heres the general idea
|
|
||||||
|
|
||||||
This runs various designs through Vivado and processes the
|
|
||||||
resulting timing informationin order to create very simple timing models.
|
|
||||||
While Vivado might have more involved models (say RC delays, fanout, etc),
|
|
||||||
timfuz creates simple models that bound realistic min and max element delays.
|
|
||||||
|
|
||||||
Currently this document focuses exclusively on fabric timing delays.
|
|
||||||
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```
|
|
||||||
make -j$(nproc)
|
|
||||||
```
|
|
||||||
|
|
||||||
This will take a relatively long time (say 45 min) and generate build/timgrid-v.json.
|
|
||||||
You can do a quicker test run (say 3 min) using:
|
|
||||||
|
|
||||||
```
|
|
||||||
make PRJ=oneblinkw PRJN=1 -j$(nproc)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Vivado background
|
|
||||||
|
|
||||||
Examples are for a XC750T on Vivado 2017.2.
|
|
||||||
|
|
||||||
TODO maybe move to: https://github.com/SymbiFlow/prjxray/wiki/Timing
|
|
||||||
|
|
||||||
|
|
||||||
### Speed index
|
|
||||||
|
|
||||||
Vivado seems to associate each delay model with a "speed index".
|
|
||||||
The fabric has these in two elements: wires (ie one delay element per tile) and pips.
|
|
||||||
For example, LUT output node A (ex: CLBLL_L_X12Y100/CLBLL_LL_A) has a single wire, also called CLBLL_L_X12Y100/CLBLL_LL_A.
|
|
||||||
This has speed index 733. Speed models can be queried and we find this corresponds to model C_CLBLL_LL_A.
|
|
||||||
|
|
||||||
There are various speed model types:
|
|
||||||
* bel_delay
|
|
||||||
* buffer
|
|
||||||
* buffer_switch
|
|
||||||
* content_version
|
|
||||||
* functional
|
|
||||||
* inpin
|
|
||||||
* outpin
|
|
||||||
* parameters
|
|
||||||
* pass_transistor
|
|
||||||
* switch
|
|
||||||
* table_lookup
|
|
||||||
* tl_buffer
|
|
||||||
* vt_limits
|
|
||||||
* wire
|
|
||||||
|
|
||||||
IIRC the interconnect is only composed of switch and wire types.
|
|
||||||
|
|
||||||
Indices with value 65535 (0xFFFF) never appear. Presumably these are unused models.
|
|
||||||
They are used for some special models such as those of type "content_version".
|
|
||||||
For example, the "xilinx" model is of type "content_version".
|
|
||||||
|
|
||||||
There are also "cost codes", but these seem to be very course (only around 30 of these)
|
|
||||||
and are suspected to be related more to PnR than timing model.
|
|
||||||
|
|
||||||
|
|
||||||
### Timing paths
|
|
||||||
|
|
||||||
The Vivado timing analyzer can easily output the following:
|
|
||||||
* Full: delay from BEL pin to BEL pin
|
|
||||||
* Interconnect only (ICO): delay from BEL pin to BEL pin, but only report interconnect delays (ie exclude site delays)
|
|
||||||
|
|
||||||
There is also theoretically an option to report delays up to a specific pip,
|
|
||||||
but this option is poorly documented and I was unable to get it to work.
|
|
||||||
|
|
||||||
Each timing path reports a fast process and a slow process min and max value. So four process values are reported in total:
|
|
||||||
* fast_max
|
|
||||||
* fast_min
|
|
||||||
* slow_max
|
|
||||||
* slow_min
|
|
||||||
|
|
||||||
For example, if the device is end of life, was poorly made, and at an extreme temperature, the delay may be up to the slow_max value.
|
|
||||||
Since ICO can be reported for each of these, fully analyzing a timing path results in 8 values.
|
|
||||||
|
|
||||||
Finally, part of this was analyzing tile regularity to discover what a reasonably compact timing model was.
|
|
||||||
We verified that all tiles of the same type have exactly the same delay elements.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Methodology
|
|
||||||
|
|
||||||
Make sure you've read the Vivado background section first
|
|
||||||
|
|
||||||
|
|
||||||
### Background
|
|
||||||
|
|
||||||
This section briefly describes some of the mathmatics used by this technique that readers may not be familiar with.
|
|
||||||
These definitions are intended to be good enough to provide a high level understanding and may not be precise.
|
|
||||||
|
|
||||||
Numerical analysis: the study of algorithms that use numerical approximation (as opposed to general symbolic manipulations)
|
|
||||||
|
|
||||||
numpy: a popular numerical analysis python library. Often written np (import numpy as np).
|
|
||||||
|
|
||||||
scipy: provides higher level functionality on top of numpy
|
|
||||||
|
|
||||||
sympy ("symbolic python"): like numpy, but is designed to work with rational numbers.
|
|
||||||
For example, python actually stores 0.1 as 0.1000000000000000055511151231257827021181583404541015625.
|
|
||||||
However, sympy can represent this as the fraction 1/10, eliminating numerical approximation issues.
|
|
||||||
|
|
||||||
Least squares (ex: scipy.optimize.least_squares): approximation method to do a best fit of several variables to a set of equations.
|
|
||||||
For example, given the equations "x = 1" and "x = 2" there isn't an exact solution.
|
|
||||||
However, "x = 1.5" is a good compromise since its reasonably solves both equations.
|
|
||||||
|
|
||||||
Linear programming (ex: scipy.optimize.linprog aka linprog): approximation method that finds a set of variables that satisfy a set of inequalities.
|
|
||||||
For example,
|
|
||||||
|
|
||||||
Reduced row echelon form (RREF, ex: sympy.Matrix.rref): the simplest form that a system of linear equations can be solved to.
|
|
||||||
For example, given "x = 1" and "x + y = 9", one can solve for "x = 1" and "y = 8".
|
|
||||||
However, given "x + y = 1" and "x + y + z = 9", there aren't enough variables to solve this fully.
|
|
||||||
In this case RREF provides a best effort by giving the ratios between correlated variables.
|
|
||||||
One variable is normalized to 1 in each of these ratios and is called the "pivot".
|
|
||||||
Note that if numpy.linalg.solve encounters an unsolvable matrix it may either complain
|
|
||||||
or generate a false solution due to numerical approximation issues.
|
|
||||||
|
|
||||||
|
|
||||||
### What didn't work
|
|
||||||
|
|
||||||
First some quick background on things that didn't work to illustrate why the current approach was chosen.
|
|
||||||
I first tried to directly through things into linprog, but it unfairly weighted towards arbitrary shared variables. For example, feeding in:
|
|
||||||
* t0 >= 10
|
|
||||||
* t0 + t1 >= 100
|
|
||||||
|
|
||||||
It would declare "t0 = 100", "t1 = 0" instead of the more intuitive "t0 = 10", "t1 = 90".
|
|
||||||
I tried to work around this in several ways, notably subtracting equations from each other to produce additional constraints.
|
|
||||||
This worked okay, but was relatively slow and wasn't approaching nearly solved solutions, even when throwing a lot of data at it.
|
|
||||||
|
|
||||||
Next we tried randomly combining a bunch of the equations together and solving them like a regular linear algebra matrix (numpy.linalg.solve).
|
|
||||||
However, this illustrated that the system was under-constrained.
|
|
||||||
Further analysis revealed that there are some delay element combinations that simply can't be linearly separated.
|
|
||||||
This was checked primarily using numpy.linalg.matrix_rank, with some use of numpy.linalg.slogdet.
|
|
||||||
matrix_rank was preferred over slogdet since its more flexible against non-square matrices.
|
|
||||||
|
|
||||||
|
|
||||||
### Process
|
|
||||||
|
|
||||||
Above ultimately led to the idea that we should come up with a set of substitutions that would make the system solvable. This has several advantages:
|
|
||||||
* Easy to evaluate which variables aren't covered well enough by source data
|
|
||||||
* Easy to evaluate which variables weren't solved properly (if its fully constrained it should have had a non-zero delay)
|
|
||||||
|
|
||||||
At a high level, the above learnings gave this process:
|
|
||||||
* Find correlated variables by using RREF (sympy.Matrix.rref) to create variable groups
|
|
||||||
- Note pivots
|
|
||||||
- You must input a fractional type (ex: fractions.Fraction, but surprisingly not int) to get exact results, otherwise it seems to fall back to numerical approximation
|
|
||||||
- This is by far the most computationally expensive step
|
|
||||||
- Mixing RREF substitutions from one data set to another may not be recommended
|
|
||||||
* Use RREF result to substitute groups on input data, creating new meta variables, but ultimately reducing the number of columns
|
|
||||||
* Pick a corner
|
|
||||||
- Examples assume fast_max, but other corners are applicable with appropriate column and sign changes
|
|
||||||
* De-duplicate by removing equations that are less constrained
|
|
||||||
- Ex: if solving for a max corner and given:
|
|
||||||
- t0 + t1 >= 10
|
|
||||||
- t0 + t1 >= 12
|
|
||||||
- The first equation is redundant since the second provides a stricter constraint
|
|
||||||
- This significantly reduces computational time
|
|
||||||
* Use least squares (scipy.optimize.least_squares) to fit variables near input constraints
|
|
||||||
- Helps fairly weight delays vs the original input constraints
|
|
||||||
- Does not guarantee all constraints are met. For example, if this was put in (ignoring these would have been de-duplicated):
|
|
||||||
- t0 = 10
|
|
||||||
- t0 = 12
|
|
||||||
- It may decide something like t0 = 11, which means that the second constraint was not satisfied given we actually want t0 >= 12
|
|
||||||
* Use linear programming (scipy.optimize.linprog aka linprog) to formally meet all remaining constraints
|
|
||||||
- Start by filtering out all constraints that are already met. This should eliminate nearly all equations
|
|
||||||
* Map resulting constraints onto different tile types
|
|
||||||
- Group delays map onto the group pivot variable, typically setting other elements to 0 (if the processed set is not the one used to create the pivots they may be non-zero)
|
|
||||||
|
|
||||||
|
|
||||||
## TODO, suggestions
|
|
||||||
|
|
||||||
Includes
|
|
||||||
* Consider removing rref
|
|
||||||
- Intended to understand what can't be solved, maybe not useful in production
|
|
||||||
* Need more coverage
|
|
||||||
- Consider instrumenting all fuzzers to output data to feed into timing anlayzer
|
|
||||||
- Justification: we need a lot of weird cases, we have code that does that in the other fuzzers
|
|
||||||
* Tune performance parameters
|
|
||||||
- Can we improve quality of results?
|
|
||||||
- Do we have a good enough quality checker? (solve_qor.py)
|
|
||||||
- Compare our vs Xilinx output on random designs
|
|
||||||
- Does the solve take too long? What could speed it up?
|
|
||||||
* Investigate min corner
|
|
||||||
- Tends to solve towards 0, making this not useful
|
|
||||||
- Low priority: most designs just close timing with setup time
|
|
||||||
* Investigate characterizing full RC timing model
|
|
||||||
* Can we split pivot delays among elements instead of entirely into pivot?
|
|
||||||
* Consider breaking out timing analyzer into its own project / library so it can be re-used on other projects
|
|
||||||
* Review "--massage". Does this help?
|
|
||||||
* Review computed site delays vs published Xilinx numbers (DC and AC Switching Characteristics)
|
|
||||||
* Fabric delay models are RC, but are the site delay models RC as well or maybe just linear?
|
|
||||||
* Can we create antenna nets to get simpler solves?
|
|
||||||
* Can we get tcl timing analyzer to analyze a partial route?
|
|
||||||
- Option says you should be able to do this
|
|
||||||
- I could not actually get it to work
|
|
||||||
|
|
||||||
|
|
||||||
### Improve test cases
|
|
||||||
|
|
||||||
Test cases are somewhat random right now. We could make much more targetted cases using custom routing to improve various fanout estimates and such.
|
|
||||||
Also there are a lot more elements that are not covered.
|
|
||||||
At a minimum these should be moved to their own directory.
|
|
||||||
|
|
||||||
|
|
||||||
### ZERO models
|
|
||||||
|
|
||||||
Background: there are a number of speed models with the name ZERO in them.
|
|
||||||
These generally seem to be zero delay, although needs more investigation.
|
|
||||||
|
|
||||||
Example: see pseudo pip item below
|
|
||||||
|
|
||||||
The timing models will probably significantly improve if these are removed.
|
|
||||||
In the past I was removing them, but decided to keep them in for now in the spirit of being more conservative.
|
|
||||||
|
|
||||||
They include:
|
|
||||||
* _BSW_CLK_ZERO
|
|
||||||
* BSW_CLK_ZERO
|
|
||||||
* _BSW_ZERO
|
|
||||||
* BSW_ZERO
|
|
||||||
* _B_ZERO
|
|
||||||
* B_ZERO
|
|
||||||
* C_CLK_ZERO
|
|
||||||
* C_DSP_ZERO
|
|
||||||
* C_ZERO
|
|
||||||
* I_ZERO
|
|
||||||
* _O_ZERO
|
|
||||||
* O_ZERO
|
|
||||||
* RC_ZERO
|
|
||||||
* _R_ZERO
|
|
||||||
* R_ZERO
|
|
||||||
|
|
||||||
|
|
||||||
### Virtual switchboxes
|
|
||||||
|
|
||||||
Background: several low level configuration details are abstracted with virtual configurable elements.
|
|
||||||
For example, LUT inputs can be rearranged to reduce routing congestion.
|
|
||||||
However, the LUT configuratioon must be changed to match the switched inputs.
|
|
||||||
This is handled by the CLBLL_L_INTER switchbox, which doesn't encode any physical configuration bits.
|
|
||||||
However, this contains PIPs with delay models.
|
|
||||||
|
|
||||||
For example, LUT A, input A1 has node CLBLM_M_A1 coming from pip junction CLBLM_M_A1 has PIP CLBLM_IMUX7->CLBLM_M_A1
|
|
||||||
with speed index 659 (R_ZERO).
|
|
||||||
|
|
||||||
This might be further evidence on related issue that ZERO models should probably be removed.
|
|
||||||
|
|
||||||
|
|
||||||
### Incporporate fanout
|
|
||||||
|
|
||||||
We could probably significantly improve model granularity by studying delay impact on fanout
|
|
||||||
|
|
||||||
|
|
||||||
### Investigate RC delays
|
|
||||||
|
|
||||||
Suspect accuracy could be significantly improved by moving to SPICE based models. But this will take significantly more characterization
|
|
||||||
|
|
||||||
|
|
||||||
### Characterize real hardware
|
|
||||||
|
|
||||||
A few people have expressed interest on running tests on real hardware. Will take some thought given we don't have direct access
|
|
||||||
|
|
||||||
|
|
||||||
### Review approximation errors
|
|
||||||
|
|
||||||
Ex: one known issue is that the objective function linearly weights small and large delays.
|
|
||||||
This is only recommended when variables are approximately the same order of magnitude.
|
|
||||||
For example, carry chain delays are on the order of 7 ps while other delays are 100 ps.
|
|
||||||
Its very easy to put a large delay on the carry chain while it could have been more appropriately put somewhere else.
|
|
||||||
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
'''
|
|
||||||
pr0ntools
|
|
||||||
Benchmarking utility
|
|
||||||
Copyright 2010 John McMaster
|
|
||||||
'''
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def time_str(delta):
|
|
||||||
fraction = delta % 1
|
|
||||||
delta -= fraction
|
|
||||||
delta = int(delta)
|
|
||||||
seconds = delta % 60
|
|
||||||
delta /= 60
|
|
||||||
minutes = delta % 60
|
|
||||||
delta /= 60
|
|
||||||
hours = delta
|
|
||||||
return '%02d:%02d:%02d.%04d' % (hours, minutes, seconds, fraction * 10000)
|
|
||||||
|
|
||||||
|
|
||||||
class Benchmark:
|
|
||||||
start_time = None
|
|
||||||
end_time = None
|
|
||||||
|
|
||||||
def __init__(self, max_items=None):
|
|
||||||
# For the lazy
|
|
||||||
self.start_time = time.time()
|
|
||||||
self.end_time = None
|
|
||||||
self.max_items = max_items
|
|
||||||
self.cur_items = 0
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.start_time = time.time()
|
|
||||||
self.end_time = None
|
|
||||||
self.cur_items = 0
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.end_time = time.time()
|
|
||||||
|
|
||||||
def advance(self, n=1):
|
|
||||||
self.cur_items += n
|
|
||||||
|
|
||||||
def set_cur_items(self, n):
|
|
||||||
self.cur_items = n
|
|
||||||
|
|
||||||
def delta_s(self):
|
|
||||||
if self.end_time:
|
|
||||||
return self.end_time - self.start_time
|
|
||||||
else:
|
|
||||||
return time.time() - self.start_time
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.end_time:
|
|
||||||
return time_str(self.end_time - self.start_time)
|
|
||||||
elif self.max_items:
|
|
||||||
cur_time = time.time()
|
|
||||||
delta_t = cur_time - self.start_time
|
|
||||||
rate_s = 'N/A'
|
|
||||||
if delta_t > 0.000001:
|
|
||||||
rate = self.cur_items / (delta_t)
|
|
||||||
rate_s = '%f items / sec' % rate
|
|
||||||
if rate == 0:
|
|
||||||
eta_str = 'inf'
|
|
||||||
else:
|
|
||||||
remaining = (self.max_items - self.cur_items) / rate
|
|
||||||
eta_str = time_str(remaining)
|
|
||||||
else:
|
|
||||||
eta_str = "indeterminate"
|
|
||||||
return '%d / %d, ETA: %s @ %s' % (
|
|
||||||
self.cur_items, self.max_items, eta_str, rate_s)
|
|
||||||
else:
|
|
||||||
return time_str(time.time() - self.start_time)
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, Ar_di2np, Ar_ds2t, A_di2ds, A_ds2di, loadc_Ads_b, index_names, A_ds2np, load_sub, run_sub_json
|
|
||||||
import numpy as np
|
|
||||||
import glob
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
from collections import OrderedDict
|
|
||||||
from fractions import Fraction
|
|
||||||
|
|
||||||
|
|
||||||
def Adi2matrix_random(A_ubd, b_ub, names):
|
|
||||||
# random assignment
|
|
||||||
# was making some empty rows
|
|
||||||
|
|
||||||
A_ret = [np.zeros(len(names)) for _i in range(len(names))]
|
|
||||||
b_ret = np.zeros(len(names))
|
|
||||||
|
|
||||||
for row, b in zip(A_ubd, b_ub):
|
|
||||||
# Randomly assign to a row
|
|
||||||
dst_rowi = random.randint(0, len(names) - 1)
|
|
||||||
rownp = Ar_di2np(row, cols=len(names), sf=1)
|
|
||||||
|
|
||||||
A_ret[dst_rowi] = np.add(A_ret[dst_rowi], rownp)
|
|
||||||
b_ret[dst_rowi] += b
|
|
||||||
return A_ret, b_ret
|
|
||||||
|
|
||||||
|
|
||||||
def Ads2matrix_linear(Ads, b):
|
|
||||||
names, Adi = A_ds2di(Ads)
|
|
||||||
cols = len(names)
|
|
||||||
rows_out = len(b)
|
|
||||||
|
|
||||||
A_ret = [np.zeros(cols) for _i in range(rows_out)]
|
|
||||||
b_ret = np.zeros(rows_out)
|
|
||||||
|
|
||||||
dst_rowi = 0
|
|
||||||
for row_di, row_b in zip(Adi, b):
|
|
||||||
row_np = Ar_di2np(row_di, cols)
|
|
||||||
|
|
||||||
A_ret[dst_rowi] = np.add(A_ret[dst_rowi], row_np)
|
|
||||||
b_ret[dst_rowi] += row_b
|
|
||||||
dst_rowi = (dst_rowi + 1) % rows_out
|
|
||||||
return A_ret, b_ret
|
|
||||||
|
|
||||||
|
|
||||||
def pmatrix(Anp, s):
|
|
||||||
import sympy
|
|
||||||
msym = sympy.Matrix(Anp)
|
|
||||||
print(s)
|
|
||||||
sympy.pprint(msym)
|
|
||||||
|
|
||||||
|
|
||||||
def pds(Ads, s):
|
|
||||||
names, Anp = A_ds2np(Ads)
|
|
||||||
pmatrix(Anp, s)
|
|
||||||
print('Names: %s' % (names, ))
|
|
||||||
|
|
||||||
|
|
||||||
def run(fns_in, sub_json=None, verbose=False):
|
|
||||||
assert len(fns_in) > 0
|
|
||||||
# arbitrary corner...data is thrown away
|
|
||||||
Ads, b = loadc_Ads_b(fns_in, "slow_max")
|
|
||||||
|
|
||||||
if sub_json:
|
|
||||||
print('Subbing JSON %u rows' % len(Ads))
|
|
||||||
#pds(Ads, 'Orig')
|
|
||||||
names_old = index_names(Ads)
|
|
||||||
run_sub_json(Ads, sub_json, verbose=verbose)
|
|
||||||
names_new = index_names(Ads)
|
|
||||||
print("Sub: %u => %u names" % (len(names_old), len(names_new)))
|
|
||||||
print(names_new)
|
|
||||||
print('Subbed JSON %u rows' % len(Ads))
|
|
||||||
names = names_new
|
|
||||||
#pds(Ads, 'Sub')
|
|
||||||
else:
|
|
||||||
names = index_names(Ads)
|
|
||||||
|
|
||||||
# Squash into a matrix
|
|
||||||
# A_ub2, b_ub2 = Adi2matrix_random(A_ubd, b, names)
|
|
||||||
Amat, _bmat = Ads2matrix_linear(Ads, b)
|
|
||||||
#pmatrix(Amat, 'Matrix')
|
|
||||||
'''
|
|
||||||
The matrix must be fully ranked to even be considered reasonable
|
|
||||||
Even then, floating point error *possibly* could make it fully ranked, although probably not since we have whole numbers
|
|
||||||
Hence the slogdet check
|
|
||||||
'''
|
|
||||||
print
|
|
||||||
# https://docs.scipy.org/doc/numpy-dev/reference/generated/numpy.linalg.matrix_rank.html
|
|
||||||
rank = np.linalg.matrix_rank(Amat)
|
|
||||||
print('rank: %s / %d col' % (rank, len(names)))
|
|
||||||
# doesn't work on non-square matrices
|
|
||||||
if 0:
|
|
||||||
# https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.linalg.slogdet.html
|
|
||||||
sign, logdet = np.linalg.slogdet(Amat)
|
|
||||||
# If the determinant is zero, then sign will be 0 and logdet will be -Inf
|
|
||||||
if sign == 0 and logdet == float('-inf'):
|
|
||||||
print('slogdet :( : 0')
|
|
||||||
else:
|
|
||||||
print('slogdet :) : %s, %s' % (sign, logdet))
|
|
||||||
if rank != len(names):
|
|
||||||
raise Exception(
|
|
||||||
"Matrix not fully ranked w/ %u / %u" % (rank, len(names)))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Check if sub.json would make a linear equation solvable')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--sub-json', help='')
|
|
||||||
parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
sub_json = None
|
|
||||||
if args.sub_json:
|
|
||||||
sub_json = load_sub(args.sub_json)
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(sub_json=sub_json, fns_in=fns_in, verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, simplify_rows, loadc_Ads_b
|
|
||||||
import glob
|
|
||||||
|
|
||||||
|
|
||||||
def run(fout, fns_in, corner, verbose=0):
|
|
||||||
Ads, b = loadc_Ads_b(fns_in, corner, ico=True)
|
|
||||||
Ads, b = simplify_rows(Ads, b, corner=corner)
|
|
||||||
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for row_b, row_ds in zip(b, Ads):
|
|
||||||
# write in same format, but just stick to this corner
|
|
||||||
out_b = [str(row_b) for _i in range(4)]
|
|
||||||
ico = '1'
|
|
||||||
items = [ico, ' '.join(out_b)]
|
|
||||||
|
|
||||||
for k, v in sorted(row_ds.items()):
|
|
||||||
items.append('%u %s' % (v, k))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Create a .csv with a single process corner')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--auto-name',
|
|
||||||
action='store_true',
|
|
||||||
help='timing4i.csv => timing4c.csv')
|
|
||||||
parser.add_argument('--out', default=None, help='Output csv')
|
|
||||||
parser.add_argument('--corner', help='Output csv')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fnout = args.out
|
|
||||||
if fnout is None:
|
|
||||||
if args.auto_name:
|
|
||||||
assert len(args.fns_in) == 1
|
|
||||||
fnin = args.fns_in[0]
|
|
||||||
fnout = fnin.replace('timing4i.csv', 'timing4c.csv')
|
|
||||||
assert fnout != fnin, 'Expect timing4i.csv in'
|
|
||||||
else:
|
|
||||||
fnout = '/dev/stdout'
|
|
||||||
print("Writing to %s" % fnout)
|
|
||||||
fout = open(fnout, 'w')
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
run(fout=fout, fns_in=fns_in, corner=args.corner, verbose=args.verbose)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, loadc_Ads_bs, index_names, load_sub, run_sub_json, instances
|
|
||||||
|
|
||||||
|
|
||||||
def gen_group(fnin, sub_json, strict=False, verbose=False):
|
|
||||||
print('Loading data')
|
|
||||||
Ads, bs = loadc_Ads_bs([fnin])
|
|
||||||
|
|
||||||
print('Sub: %u rows' % len(Ads))
|
|
||||||
iold = instances(Ads)
|
|
||||||
names_old = index_names(Ads)
|
|
||||||
run_sub_json(Ads, sub_json, strict=strict, verbose=verbose)
|
|
||||||
names = index_names(Ads)
|
|
||||||
print("Sub: %u => %u names" % (len(names_old), len(names)))
|
|
||||||
print('Sub: %u => %u instances' % (iold, instances(Ads)))
|
|
||||||
|
|
||||||
for row_ds, row_bs in zip(Ads, bs):
|
|
||||||
yield row_ds, row_bs
|
|
||||||
|
|
||||||
|
|
||||||
def run(fns_in, fnout, sub_json, strict=False, verbose=False):
|
|
||||||
with open(fnout, 'w') as fout:
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for fn_in in fns_in:
|
|
||||||
for row_ds, row_bs in gen_group(fn_in, sub_json, strict=strict):
|
|
||||||
row_ico = 1
|
|
||||||
items = [str(row_ico), ' '.join([str(x) for x in row_bs])]
|
|
||||||
for k, v in sorted(row_ds.items()):
|
|
||||||
items.append('%u %s' % (v, k))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Substitute .csv to group correlated variables')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--strict', action='store_true', help='')
|
|
||||||
parser.add_argument('--sub-csv', help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--sub-json',
|
|
||||||
required=True,
|
|
||||||
help='Group substitutions to make fully ranked')
|
|
||||||
parser.add_argument('--out', help='Output sub.json substitution result')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
sub_json = load_sub(args.sub_json)
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(
|
|
||||||
fns_in,
|
|
||||||
args.out,
|
|
||||||
sub_json=sub_json,
|
|
||||||
strict=args.strict,
|
|
||||||
verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, loadc_Ads_bs, load_sub, Ads2bounds, corners2csv, corner_s2i
|
|
||||||
|
|
||||||
|
|
||||||
def gen_flat(fns_in, sub_json, corner=None):
|
|
||||||
Ads, bs = loadc_Ads_bs(fns_in)
|
|
||||||
bounds = Ads2bounds(Ads, bs)
|
|
||||||
# Elements with zero delay assigned due to sub group
|
|
||||||
group_zeros = set()
|
|
||||||
# Elements with a concrete delay
|
|
||||||
nonzeros = set()
|
|
||||||
|
|
||||||
if corner:
|
|
||||||
zero_row = [None, None, None, None]
|
|
||||||
zero_row[corner_s2i[corner]] = 0
|
|
||||||
else:
|
|
||||||
zero_row = None
|
|
||||||
|
|
||||||
for bound_name, bound_bs in bounds.items():
|
|
||||||
sub = sub_json['subs'].get(bound_name, None)
|
|
||||||
if bound_name in sub_json['zero_names']:
|
|
||||||
if zero_row:
|
|
||||||
yield bound_name, 0
|
|
||||||
elif sub:
|
|
||||||
#print('sub', sub)
|
|
||||||
# put entire delay into pivot
|
|
||||||
pivot = sub_json['pivots'][bound_name]
|
|
||||||
assert pivot not in group_zeros
|
|
||||||
nonzeros.add(pivot)
|
|
||||||
non_pivot = set(sub.keys() - set([pivot]))
|
|
||||||
#for name in non_pivot:
|
|
||||||
# assert name not in nonzeros, (pivot, name, nonzeros)
|
|
||||||
group_zeros.update(non_pivot)
|
|
||||||
#print('yield PIVOT', pivot)
|
|
||||||
yield pivot, bound_bs
|
|
||||||
else:
|
|
||||||
nonzeros.add(bound_name)
|
|
||||||
yield bound_name, bound_bs
|
|
||||||
# non-pivots can appear multiple times, but they should always be zero
|
|
||||||
# however, due to substitution limitations, just warn
|
|
||||||
violations = group_zeros.intersection(nonzeros)
|
|
||||||
if len(violations):
|
|
||||||
print('WARNING: %s non-0 non-pivot' % (len(violations)))
|
|
||||||
|
|
||||||
# XXX: how to best handle these?
|
|
||||||
# should they be fixed 0?
|
|
||||||
if zero_row:
|
|
||||||
# ZERO names should always be zero
|
|
||||||
#print('ZEROs: %u' % len(sub_json['zero_names']))
|
|
||||||
for zero in sub_json['zero_names']:
|
|
||||||
#print('yield ZERO', zero)
|
|
||||||
yield zero, zero_row
|
|
||||||
|
|
||||||
real_zeros = group_zeros - violations
|
|
||||||
print(
|
|
||||||
'Zero candidates: %u w/ %u non-pivot conflicts => %u zeros as solved'
|
|
||||||
% (len(group_zeros), len(violations), len(real_zeros)))
|
|
||||||
# Only yield elements not already yielded
|
|
||||||
for zero in real_zeros:
|
|
||||||
#print('yield solve-0', zero)
|
|
||||||
yield zero, zero_row
|
|
||||||
|
|
||||||
|
|
||||||
def run(fns_in, fnout, sub_json, corner=None, verbose=False):
|
|
||||||
with open(fnout, 'w') as fout:
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for name, corners in sorted(list(gen_flat(fns_in, sub_json,
|
|
||||||
corner=corner))):
|
|
||||||
row_ico = 1
|
|
||||||
items = [str(row_ico), corners2csv(corners)]
|
|
||||||
items.append('%u %s' % (1, name))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Substitute .csv to ungroup correlated variables')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--sub-csv', help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--sub-json',
|
|
||||||
required=True,
|
|
||||||
help='Group substitutions to make fully ranked')
|
|
||||||
parser.add_argument('--corner', default=None, help='')
|
|
||||||
parser.add_argument('--out', default=None, help='output timing delay .csv')
|
|
||||||
parser.add_argument(
|
|
||||||
'fns_in',
|
|
||||||
nargs='+',
|
|
||||||
help='input timing delay .csv (NOTE: must be single column)')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
sub_json = load_sub(args.sub_json)
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(
|
|
||||||
args.fns_in,
|
|
||||||
args.out,
|
|
||||||
sub_json=sub_json,
|
|
||||||
verbose=args.verbose,
|
|
||||||
corner=args.corner)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# Demonstrates that cells can have 0 to 2 BEL output pins
|
|
||||||
# roi/dout_shr_reg[0]: roi/dout_shr[0]_i_1/O roi/dout_shr_reg[0]
|
|
||||||
|
|
||||||
set TIME_start [clock clicks -milliseconds]
|
|
||||||
set site_src_nets 0
|
|
||||||
set neti 0
|
|
||||||
set nets [get_nets -hierarchical]
|
|
||||||
set nnets [llength $nets]
|
|
||||||
set opins_zero 0
|
|
||||||
set opins_multi 0
|
|
||||||
foreach net $nets {
|
|
||||||
incr neti
|
|
||||||
puts "Net $neti / $nnets: $net"
|
|
||||||
|
|
||||||
set out_pins [get_pins -filter {DIRECTION == OUT} -of_objects $net]
|
|
||||||
set npins [llength $out_pins]
|
|
||||||
if {$npins == 0} {
|
|
||||||
puts " $net zero source pins: $src_pin"
|
|
||||||
incr opins_zero
|
|
||||||
}
|
|
||||||
if {$npins > 1} {
|
|
||||||
puts " $net multi source pins: $src_pin"
|
|
||||||
incr opins_multi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
|
|
||||||
puts "Took ms: $TIME_taken"
|
|
||||||
puts "Result: $opins_zero / $nnets zero"
|
|
||||||
puts "Result: $opins_multi / $nnets multi"
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import scipy.optimize as optimize
|
|
||||||
import numpy as np
|
|
||||||
import glob
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def run_max(Anp, b, verbose=False):
|
|
||||||
cols = len(Anp[0])
|
|
||||||
|
|
||||||
b_ub = -1.0 * b
|
|
||||||
A_ub = [-1.0 * x for x in Anp]
|
|
||||||
|
|
||||||
res = optimize.linprog(
|
|
||||||
c=[1 for _i in range(cols)],
|
|
||||||
A_ub=A_ub,
|
|
||||||
b_ub=b_ub,
|
|
||||||
bounds=(0, None),
|
|
||||||
options={
|
|
||||||
"disp": True,
|
|
||||||
'maxiter': 1000000,
|
|
||||||
'bland': True,
|
|
||||||
'tol': 1e-6,
|
|
||||||
})
|
|
||||||
nonzeros = 0
|
|
||||||
print('Ran')
|
|
||||||
if res.success:
|
|
||||||
print('Result sample (%d elements)' % (len(res.x)))
|
|
||||||
plim = 3
|
|
||||||
for xi, x in enumerate(res.x):
|
|
||||||
nonzero = x >= 0.001
|
|
||||||
if nonzero:
|
|
||||||
nonzeros += 1
|
|
||||||
if nonzero and (verbose or (
|
|
||||||
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
|
|
||||||
print(' % 4u % 10.1f' % (xi, x))
|
|
||||||
print('Delay on %d / %d' % (nonzeros, len(res.x)))
|
|
||||||
|
|
||||||
|
|
||||||
def run_min(Anp, b, verbose=False):
|
|
||||||
cols = len(Anp[0])
|
|
||||||
|
|
||||||
b_ub = b
|
|
||||||
A_ub = Anp
|
|
||||||
|
|
||||||
res = optimize.linprog(
|
|
||||||
c=[-1 for _i in range(cols)],
|
|
||||||
A_ub=A_ub,
|
|
||||||
b_ub=b_ub,
|
|
||||||
bounds=(0, None),
|
|
||||||
options={
|
|
||||||
"disp": True,
|
|
||||||
'maxiter': 1000000,
|
|
||||||
'bland': True,
|
|
||||||
'tol': 1e-6,
|
|
||||||
})
|
|
||||||
nonzeros = 0
|
|
||||||
print('Ran')
|
|
||||||
if res.success:
|
|
||||||
print('Result sample (%d elements)' % (len(res.x)))
|
|
||||||
plim = 3
|
|
||||||
for xi, x in enumerate(res.x):
|
|
||||||
nonzero = x >= 0.001
|
|
||||||
if nonzero:
|
|
||||||
nonzeros += 1
|
|
||||||
if nonzero and (verbose or (
|
|
||||||
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
|
|
||||||
print(' % 4u % 10.1f' % (xi, x))
|
|
||||||
print('Delay on %d / %d' % (nonzeros, len(res.x)))
|
|
||||||
|
|
||||||
|
|
||||||
def run(verbose=False):
|
|
||||||
'''
|
|
||||||
1 * x0 = 10
|
|
||||||
1 * x0 + 1 * x1 = 100
|
|
||||||
1 * x0 = 40
|
|
||||||
2 * x1 = 140
|
|
||||||
'''
|
|
||||||
Anp = np.array([
|
|
||||||
[1, 0],
|
|
||||||
[1, 1],
|
|
||||||
[1, 0],
|
|
||||||
[0, 2],
|
|
||||||
])
|
|
||||||
b = np.array([
|
|
||||||
10,
|
|
||||||
100,
|
|
||||||
40,
|
|
||||||
140,
|
|
||||||
])
|
|
||||||
'''
|
|
||||||
Max
|
|
||||||
Optimization terminated successfully.
|
|
||||||
Current function value: 110.000000
|
|
||||||
Iterations: 4
|
|
||||||
Ran
|
|
||||||
Result sample (2 elements)
|
|
||||||
0 40.0
|
|
||||||
1 70.0
|
|
||||||
Delay on 2 / 2
|
|
||||||
'''
|
|
||||||
print('Max')
|
|
||||||
run_max(Anp, b)
|
|
||||||
print('')
|
|
||||||
print('')
|
|
||||||
print('')
|
|
||||||
'''
|
|
||||||
Min
|
|
||||||
Optimization terminated successfully.
|
|
||||||
Current function value: -80.000000
|
|
||||||
Iterations: 2
|
|
||||||
Ran
|
|
||||||
Result sample (2 elements)
|
|
||||||
0 10.0
|
|
||||||
1 70.0
|
|
||||||
Delay on 2 / 2
|
|
||||||
'''
|
|
||||||
print('Min')
|
|
||||||
run_min(Anp, b)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Test')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(verbose=args.verbose)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
N := 1
|
|
||||||
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
|
|
||||||
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
|
|
||||||
|
|
||||||
all: $(SPECIMENS_OK)
|
|
||||||
|
|
||||||
$(SPECIMENS_OK):
|
|
||||||
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
|
|
||||||
touch $@
|
|
||||||
|
|
||||||
run:
|
|
||||||
$(MAKE) clean
|
|
||||||
$(MAKE) all
|
|
||||||
touch run.ok
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf specimen_[0-9][0-9][0-9]/ seg_clblx.db __pycache__ run.ok
|
|
||||||
rm -rf vivado*.log vivado_*.str vivado*.jou design *.bits *.dcp *.bit
|
|
||||||
rm -rf build
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
Characterizes how attributes vary across pips, wires, and nodes. Usage:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ make
|
|
||||||
$ python3 pip_unique.py
|
|
||||||
$ python3 wire_unique.py
|
|
||||||
$ python3 node_unique.py
|
|
||||||
```
|
|
||||||
|
|
||||||
NOTE: "make" will take a long time (about 2.5 hours on my machine)
|
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
source ${XRAY_GENHEADER}
|
|
||||||
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
|
|
||||||
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
source ../../../../utils/utils.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog ../../src/picorv32.v
|
|
||||||
read_verilog ../top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
proc pips_all {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/pip_all.txt" w]
|
|
||||||
set items [get_pips]
|
|
||||||
puts "Items: [llength $items]"
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
set properties [list_property [lindex $items 0]]
|
|
||||||
foreach item $items {
|
|
||||||
set needspace 0
|
|
||||||
foreach property $properties {
|
|
||||||
set val [get_property $property $item]
|
|
||||||
if {"$val" ne ""} {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp "$property:$val"
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
proc wires_all {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/wire_all.txt" w]
|
|
||||||
set items [get_wires]
|
|
||||||
puts "Items: [llength $items]"
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
set properties [list_property [lindex $items 0]]
|
|
||||||
foreach item $items {
|
|
||||||
set needspace 0
|
|
||||||
foreach property $properties {
|
|
||||||
set val [get_property $property $item]
|
|
||||||
if {"$val" ne ""} {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp "$property:$val"
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
pips_all
|
|
||||||
wires_all
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def gen_nodes(fin):
|
|
||||||
for l in fin:
|
|
||||||
lj = {}
|
|
||||||
l = l.strip()
|
|
||||||
for kvs in l.split():
|
|
||||||
name, value = kvs.split(':')
|
|
||||||
'''
|
|
||||||
NAME:LIOB33_SING_X0Y199/IOB_IBUF0
|
|
||||||
|
|
||||||
IS_BAD:0
|
|
||||||
IS_COMPLETE:1
|
|
||||||
IS_GND:0 IS_INPUT_PIN:1 IS_OUTPUT_PIN:0 IS_PIN:1 IS_VCC:0
|
|
||||||
NUM_WIRES:2
|
|
||||||
PIN_WIRE:1
|
|
||||||
'''
|
|
||||||
if name in ('COST_CODE', 'SPEED_CLASS'):
|
|
||||||
value = int(value)
|
|
||||||
lj[name] = value
|
|
||||||
|
|
||||||
tile_type, xy, wname = re.match(
|
|
||||||
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
|
|
||||||
lj['tile_type'] = tile_type
|
|
||||||
lj['xy'] = xy
|
|
||||||
lj['wname'] = wname
|
|
||||||
|
|
||||||
lj['l'] = l
|
|
||||||
|
|
||||||
yield lj
|
|
||||||
|
|
||||||
|
|
||||||
def run(node_fin, verbose=0):
|
|
||||||
refnodes = {}
|
|
||||||
nodei = 0
|
|
||||||
for nodei, anode in enumerate(gen_nodes(node_fin)):
|
|
||||||
|
|
||||||
def getk(anode):
|
|
||||||
return anode['wname']
|
|
||||||
#return (anode['tile_type'], anode['wname'])
|
|
||||||
|
|
||||||
if nodei % 100000 == 0:
|
|
||||||
print('Check node %d, %u node types' % (nodei, len(refnodes)))
|
|
||||||
# Existing node?
|
|
||||||
try:
|
|
||||||
refnode = refnodes[getk(anode)]
|
|
||||||
except KeyError:
|
|
||||||
# Set as reference
|
|
||||||
refnodes[getk(anode)] = anode
|
|
||||||
continue
|
|
||||||
# Verify equivilence
|
|
||||||
for k in (
|
|
||||||
'SPEED_CLASS',
|
|
||||||
'COST_CODE',
|
|
||||||
'COST_CODE_NAME',
|
|
||||||
'IS_BAD',
|
|
||||||
'IS_COMPLETE',
|
|
||||||
'IS_GND',
|
|
||||||
'IS_VCC',
|
|
||||||
):
|
|
||||||
if k in refnode and k in anode:
|
|
||||||
|
|
||||||
def fail():
|
|
||||||
print('Mismatch on %s' % k)
|
|
||||||
print(refnode[k], anode[k])
|
|
||||||
print(refnode['l'])
|
|
||||||
print(anode['l'])
|
|
||||||
#assert 0
|
|
||||||
|
|
||||||
if k == 'SPEED_CLASS':
|
|
||||||
# Parameters known to effect SPEED_CLASS
|
|
||||||
# Verify at least one parameter is different
|
|
||||||
if refnode[k] != anode[k]:
|
|
||||||
for k2 in ('IS_PIN', 'IS_INPUT_PIN', 'IS_OUTPUT_PIN',
|
|
||||||
'PIN_WIRE', 'NUM_WIRES'):
|
|
||||||
if refnode[k2] != anode[k2]:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if 0:
|
|
||||||
print
|
|
||||||
fail()
|
|
||||||
elif refnode[k] != anode[k]:
|
|
||||||
print
|
|
||||||
fail()
|
|
||||||
# A key in one but not the other?
|
|
||||||
elif k in refnode or k in anode:
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Determines which info is consistent across nodes with the same name')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'node_fn_in',
|
|
||||||
default='specimen_001/wire_all.txt',
|
|
||||||
nargs='?',
|
|
||||||
help='Input file')
|
|
||||||
args = parser.parse_args()
|
|
||||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
'''
|
|
||||||
Triaging tool to help understand where we need more timing coverage
|
|
||||||
Finds correlated variables to help make better test cases
|
|
||||||
'''
|
|
||||||
|
|
||||||
from timfuz import Benchmark, Ar_di2np, loadc_Ads_b, index_names, A_ds2np, simplify_rows
|
|
||||||
import numpy as np
|
|
||||||
import glob
|
|
||||||
import math
|
|
||||||
import json
|
|
||||||
import sympy
|
|
||||||
from collections import OrderedDict
|
|
||||||
from fractions import Fraction
|
|
||||||
import random
|
|
||||||
from sympy import Rational
|
|
||||||
|
|
||||||
|
|
||||||
def intr(r):
|
|
||||||
DELTA = 0.0001
|
|
||||||
|
|
||||||
for i, x in enumerate(r):
|
|
||||||
if type(x) is float:
|
|
||||||
xi = int(x)
|
|
||||||
assert abs(xi - x) < DELTA
|
|
||||||
r[i] = xi
|
|
||||||
|
|
||||||
|
|
||||||
def fracr(r):
|
|
||||||
intr(r)
|
|
||||||
return [Fraction(x) for x in r]
|
|
||||||
|
|
||||||
|
|
||||||
def fracm(m):
|
|
||||||
return [fracr(r) for r in m]
|
|
||||||
|
|
||||||
|
|
||||||
def symratr(r):
|
|
||||||
intr(r)
|
|
||||||
return [Rational(x) for x in r]
|
|
||||||
|
|
||||||
|
|
||||||
def symratm(m):
|
|
||||||
return [symratr(r) for r in m]
|
|
||||||
|
|
||||||
|
|
||||||
def intm(m):
|
|
||||||
[intr(r) for r in m]
|
|
||||||
return m
|
|
||||||
|
|
||||||
|
|
||||||
def create_matrix(rows, cols):
|
|
||||||
ret = np.zeros((rows, cols))
|
|
||||||
for rowi in range(rows):
|
|
||||||
for coli in range(cols):
|
|
||||||
ret[rowi][coli] = random.randint(1, 10)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def create_matrix_sparse(rows, cols):
|
|
||||||
ret = np.zeros((rows, cols))
|
|
||||||
for rowi in range(rows):
|
|
||||||
for coli in range(cols):
|
|
||||||
if random.randint(0, 5) < 1:
|
|
||||||
ret[rowi][coli] = random.randint(1, 10)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def run(
|
|
||||||
rows=35,
|
|
||||||
cols=200,
|
|
||||||
verbose=False,
|
|
||||||
encoding='np',
|
|
||||||
sparse=False,
|
|
||||||
normalize_last=True):
|
|
||||||
random.seed(0)
|
|
||||||
if sparse:
|
|
||||||
mnp = create_matrix_sparse(rows, cols)
|
|
||||||
else:
|
|
||||||
mnp = create_matrix(rows, cols)
|
|
||||||
#print(mnp[0])
|
|
||||||
|
|
||||||
if encoding == 'fraction':
|
|
||||||
msym = sympy.Matrix(fracm(mnp))
|
|
||||||
elif encoding == 'np':
|
|
||||||
msym = sympy.Matrix(mnp)
|
|
||||||
elif encoding == 'sympy':
|
|
||||||
msym = sympy.Matrix(symratm(mnp))
|
|
||||||
# this actually produces float results
|
|
||||||
elif encoding == 'int':
|
|
||||||
msym = sympy.Matrix(intm(mnp))
|
|
||||||
else:
|
|
||||||
assert 0, 'bad encoding: %s' % encoding
|
|
||||||
print(type(msym[0]), str(msym[0]))
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('names')
|
|
||||||
print(names)
|
|
||||||
print('Matrix')
|
|
||||||
sympy.pprint(msym)
|
|
||||||
|
|
||||||
print(
|
|
||||||
'%s matrix, %u rows x %u cols, sparse: %s, normlast: %s' %
|
|
||||||
(encoding, len(mnp), len(mnp[0]), sparse, normalize_last))
|
|
||||||
bench = Benchmark()
|
|
||||||
try:
|
|
||||||
rref, pivots = msym.rref(normalize_last=normalize_last)
|
|
||||||
finally:
|
|
||||||
print('rref exiting after %s' % bench)
|
|
||||||
print(type(rref[0]), str(rref[0]))
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('Pivots')
|
|
||||||
sympy.pprint(pivots)
|
|
||||||
print('rref')
|
|
||||||
sympy.pprint(rref)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Matrix solving performance tests')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--sparse', action='store_true', help='')
|
|
||||||
parser.add_argument('--rows', type=int, help='')
|
|
||||||
parser.add_argument('--cols', type=int, help='')
|
|
||||||
parser.add_argument('--normalize-last', type=int, help='')
|
|
||||||
parser.add_argument('--encoding', default='np', help='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(
|
|
||||||
encoding=args.encoding,
|
|
||||||
rows=args.rows,
|
|
||||||
cols=args.cols,
|
|
||||||
sparse=args.sparse,
|
|
||||||
normalize_last=bool(args.normalize_last),
|
|
||||||
verbose=args.verbose)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def gen_wires(fin):
|
|
||||||
for l in fin:
|
|
||||||
lj = {}
|
|
||||||
l = l.strip()
|
|
||||||
for kvs in l.split():
|
|
||||||
name, value = kvs.split(':')
|
|
||||||
lj[name] = value
|
|
||||||
|
|
||||||
tile_type, xy, wname = re.match(
|
|
||||||
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
|
|
||||||
lj['tile_type'] = tile_type
|
|
||||||
lj['xy'] = xy
|
|
||||||
lj['wname'] = wname
|
|
||||||
|
|
||||||
lj['l'] = l
|
|
||||||
|
|
||||||
yield lj
|
|
||||||
|
|
||||||
|
|
||||||
def run(node_fin, verbose=0):
|
|
||||||
refnodes = {}
|
|
||||||
nodei = 0
|
|
||||||
for nodei, anode in enumerate(gen_wires(node_fin)):
|
|
||||||
|
|
||||||
def getk(anode):
|
|
||||||
return anode['wname']
|
|
||||||
return (anode['tile_type'], anode['wname'])
|
|
||||||
|
|
||||||
if nodei % 100000 == 0:
|
|
||||||
print('Check node %d, %u node types' % (nodei, len(refnodes)))
|
|
||||||
# Existing node?
|
|
||||||
try:
|
|
||||||
refnode = refnodes[getk(anode)]
|
|
||||||
except KeyError:
|
|
||||||
# Set as reference
|
|
||||||
refnodes[getk(anode)] = anode
|
|
||||||
continue
|
|
||||||
k_invariant = (
|
|
||||||
'CAN_INVERT',
|
|
||||||
'IS_BUFFERED_2_0',
|
|
||||||
'IS_BUFFERED_2_1',
|
|
||||||
'IS_DIRECTIONAL',
|
|
||||||
'IS_EXCLUDED_PIP',
|
|
||||||
'IS_FIXED_INVERSION',
|
|
||||||
'IS_INVERTED',
|
|
||||||
'IS_PSEUDO',
|
|
||||||
'IS_SITE_PIP',
|
|
||||||
'IS_TEST_PIP',
|
|
||||||
)
|
|
||||||
k_varies = ('TILE', )
|
|
||||||
# Verify equivilence
|
|
||||||
for k in k_invariant:
|
|
||||||
if k in refnode and k in anode:
|
|
||||||
|
|
||||||
def fail():
|
|
||||||
print('Mismatch on %s' % k)
|
|
||||||
print(refnode[k], anode[k])
|
|
||||||
print(refnode['l'])
|
|
||||||
print(anode['l'])
|
|
||||||
#assert 0
|
|
||||||
|
|
||||||
if refnode[k] != anode[k]:
|
|
||||||
print('')
|
|
||||||
fail()
|
|
||||||
# A key in one but not the other?
|
|
||||||
elif k in refnode or k in anode:
|
|
||||||
assert 0
|
|
||||||
elif k not in k_varies:
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Determines which info is consistent across PIPs with the same name')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'node_fn_in',
|
|
||||||
default='specimen_001/pip_all.txt',
|
|
||||||
nargs='?',
|
|
||||||
help='Input file')
|
|
||||||
args = parser.parse_args()
|
|
||||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
//move some stuff to minitests/ncy0
|
|
||||||
|
|
||||||
`define SEED 32'h12345678
|
|
||||||
|
|
||||||
module top(input clk, stb, di, output do);
|
|
||||||
localparam integer DIN_N = 42;
|
|
||||||
localparam integer DOUT_N = 79;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
|
|
||||||
roi #(.DIN_N(DIN_N), .DOUT_N(DOUT_N))
|
|
||||||
roi (
|
|
||||||
.clk(clk),
|
|
||||||
.din(din),
|
|
||||||
.dout(dout)
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module roi(input clk, input [DIN_N-1:0] din, output [DOUT_N-1:0] dout);
|
|
||||||
parameter integer DIN_N = -1;
|
|
||||||
parameter integer DOUT_N = -1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
//Take out for now to make sure LUTs are more predictable
|
|
||||||
picorv32 picorv32 (
|
|
||||||
.clk(clk),
|
|
||||||
.resetn(din[0]),
|
|
||||||
.mem_valid(dout[0]),
|
|
||||||
.mem_instr(dout[1]),
|
|
||||||
.mem_ready(din[1]),
|
|
||||||
.mem_addr(dout[33:2]),
|
|
||||||
.mem_wdata(dout[66:34]),
|
|
||||||
.mem_wstrb(dout[70:67]),
|
|
||||||
.mem_rdata(din[33:2])
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
randluts randluts (
|
|
||||||
.din(din[41:34]),
|
|
||||||
.dout(dout[78:71])
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
randluts #(.N(150)) randluts (
|
|
||||||
.din(din[41:34]),
|
|
||||||
.dout(dout[78:71])
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module randluts(input [7:0] din, output [7:0] dout);
|
|
||||||
parameter integer N = 250;
|
|
||||||
|
|
||||||
function [31:0] xorshift32(input [31:0] xorin);
|
|
||||||
begin
|
|
||||||
xorshift32 = xorin;
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 << 13);
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 >> 17);
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 << 5);
|
|
||||||
end
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function [63:0] lutinit(input [7:0] a, b);
|
|
||||||
begin
|
|
||||||
lutinit[63:32] = xorshift32(xorshift32(xorshift32(xorshift32({a, b} ^ `SEED))));
|
|
||||||
lutinit[31: 0] = xorshift32(xorshift32(xorshift32(xorshift32({b, a} ^ `SEED))));
|
|
||||||
end
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
wire [(N+1)*8-1:0] nets;
|
|
||||||
|
|
||||||
assign nets[7:0] = din;
|
|
||||||
assign dout = nets[(N+1)*8-1:N*8];
|
|
||||||
|
|
||||||
genvar i, j;
|
|
||||||
generate
|
|
||||||
for (i = 0; i < N; i = i+1) begin:is
|
|
||||||
for (j = 0; j < 8; j = j+1) begin:js
|
|
||||||
localparam integer k = xorshift32(xorshift32(xorshift32(xorshift32((i << 20) ^ (j << 10) ^ `SEED)))) & 255;
|
|
||||||
(* KEEP, DONT_TOUCH *)
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(lutinit(i, j))
|
|
||||||
) lut (
|
|
||||||
.I0(nets[8*i+(k+0)%8]),
|
|
||||||
.I1(nets[8*i+(k+1)%8]),
|
|
||||||
.I2(nets[8*i+(k+2)%8]),
|
|
||||||
.I3(nets[8*i+(k+3)%8]),
|
|
||||||
.I4(nets[8*i+(k+4)%8]),
|
|
||||||
.I5(nets[8*i+(k+5)%8]),
|
|
||||||
.O(nets[8*i+8+j])
|
|
||||||
);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
endgenerate
|
|
||||||
endmodule
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def gen_wires(fin):
|
|
||||||
for l in fin:
|
|
||||||
lj = {}
|
|
||||||
l = l.strip()
|
|
||||||
for kvs in l.split():
|
|
||||||
name, value = kvs.split(':')
|
|
||||||
lj[name] = value
|
|
||||||
|
|
||||||
tile_type, xy, wname = re.match(
|
|
||||||
r'(.*)_(X[0-9]*Y[0-9]*)/(.*)', lj['NAME']).groups()
|
|
||||||
lj['tile_type'] = tile_type
|
|
||||||
lj['xy'] = xy
|
|
||||||
lj['wname'] = wname
|
|
||||||
|
|
||||||
lj['l'] = l
|
|
||||||
|
|
||||||
yield lj
|
|
||||||
|
|
||||||
|
|
||||||
def run(node_fin, verbose=0):
|
|
||||||
refnodes = {}
|
|
||||||
nodei = 0
|
|
||||||
for nodei, anode in enumerate(gen_wires(node_fin)):
|
|
||||||
|
|
||||||
def getk(anode):
|
|
||||||
return anode['wname']
|
|
||||||
#return (anode['tile_type'], anode['wname'])
|
|
||||||
|
|
||||||
if nodei % 100000 == 0:
|
|
||||||
print('Check node %d, %u node types' % (nodei, len(refnodes)))
|
|
||||||
# Existing node?
|
|
||||||
try:
|
|
||||||
refnode = refnodes[getk(anode)]
|
|
||||||
except KeyError:
|
|
||||||
# Set as reference
|
|
||||||
refnodes[getk(anode)] = anode
|
|
||||||
continue
|
|
||||||
k_invariant = (
|
|
||||||
'COST_CODE',
|
|
||||||
'IS_INPUT_PIN',
|
|
||||||
'IS_OUTPUT_PIN',
|
|
||||||
'IS_PART_OF_BUS',
|
|
||||||
'NUM_INTERSECTS',
|
|
||||||
'NUM_TILE_PORTS',
|
|
||||||
'SPEED_INDEX',
|
|
||||||
'TILE_PATTERN_OFFSET',
|
|
||||||
)
|
|
||||||
k_varies = (
|
|
||||||
'ID_IN_TILE_TYPE',
|
|
||||||
'IS_CONNECTED',
|
|
||||||
'NUM_DOWNHILL_PIPS',
|
|
||||||
'NUM_PIPS',
|
|
||||||
'NUM_UPHILL_PIPS',
|
|
||||||
'TILE_NAME',
|
|
||||||
)
|
|
||||||
# Verify equivilence
|
|
||||||
for k in k_invariant:
|
|
||||||
if k in refnode and k in anode:
|
|
||||||
|
|
||||||
def fail():
|
|
||||||
print('Mismatch on %s' % k)
|
|
||||||
print(refnode[k], anode[k])
|
|
||||||
print(refnode['l'])
|
|
||||||
print(anode['l'])
|
|
||||||
#assert 0
|
|
||||||
|
|
||||||
if refnode[k] != anode[k]:
|
|
||||||
print
|
|
||||||
fail()
|
|
||||||
# A key in one but not the other?
|
|
||||||
elif k in refnode or k in anode:
|
|
||||||
assert 0
|
|
||||||
elif k not in k_varies:
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Determines which info is consistent across wires with the same name')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'node_fn_in',
|
|
||||||
default='specimen_001/wire_all.txt',
|
|
||||||
nargs='?',
|
|
||||||
help='Input file')
|
|
||||||
args = parser.parse_args()
|
|
||||||
run(open(args.node_fn_in, 'r'), verbose=args.verbose)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Collect info on ZERO speed classes
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def run_types(tilej, verbose=False):
|
|
||||||
def process(etype):
|
|
||||||
# dict[model] = set((tile, wire/pip))
|
|
||||||
zeros = {}
|
|
||||||
print('Processing %s' % etype)
|
|
||||||
# Index delay models by type, recording where they occured
|
|
||||||
for tilek, tilev in tilej['tiles'].items():
|
|
||||||
for ename, emodel in tilev[etype].items():
|
|
||||||
if emodel.find('ZERO') >= 0:
|
|
||||||
zeros.setdefault(emodel, set()).add((tilek, ename))
|
|
||||||
# Print out delay model instances
|
|
||||||
print('%s ZERO types: %u, %s' % (etype, len(zeros), zeros.keys()))
|
|
||||||
print(
|
|
||||||
'%s ZERO instances: %u' %
|
|
||||||
(etype, sum([len(x) for x in zeros.values()])))
|
|
||||||
for model in sorted(zeros.keys()):
|
|
||||||
modelv = zeros[model]
|
|
||||||
print('Model: %s' % model)
|
|
||||||
for tile_name, element_name in sorted(list(modelv)):
|
|
||||||
print(' %s: %s' % (tile_name, element_name))
|
|
||||||
|
|
||||||
process('wires')
|
|
||||||
print('')
|
|
||||||
process('pips')
|
|
||||||
|
|
||||||
|
|
||||||
def run_prefix(tilej, verbose=False):
|
|
||||||
def process(etype):
|
|
||||||
prefixes = set()
|
|
||||||
print('Processing %s' % etype)
|
|
||||||
# Index delay models by type, recording where they occured
|
|
||||||
for tilek, tilev in tilej['tiles'].items():
|
|
||||||
for ename, emodel in tilev[etype].items():
|
|
||||||
prefix = emodel.split('_')[0]
|
|
||||||
prefixes.add(prefix)
|
|
||||||
print('%s prefixes: %u' % (etype, len(prefixes)))
|
|
||||||
for prefix in sorted(prefixes):
|
|
||||||
print(' %s' % prefix)
|
|
||||||
|
|
||||||
process('wires')
|
|
||||||
print('')
|
|
||||||
process('pips')
|
|
||||||
|
|
||||||
|
|
||||||
def run(fnin, verbose=False):
|
|
||||||
tilej = json.load((open(fnin, 'r')))
|
|
||||||
run_types(tilej)
|
|
||||||
print('')
|
|
||||||
print('')
|
|
||||||
run_prefix(tilej)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Solve timing solution')
|
|
||||||
parser.add_argument(
|
|
||||||
'fnin',
|
|
||||||
default="../timgrid/build/timgrid-s.json",
|
|
||||||
nargs='?',
|
|
||||||
help='input timgrid JSON')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(args.fnin, verbose=False)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
specimen_*
|
|
||||||
build
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
all:
|
|
||||||
echo "FIXME: tie projects together"
|
|
||||||
false
|
|
||||||
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# Run corner specific calculations
|
|
||||||
|
|
||||||
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
|
|
||||||
CORNER=slow_max
|
|
||||||
ALLOW_ZERO_EQN?=N
|
|
||||||
BADPRJ_OK?=N
|
|
||||||
BUILD_DIR?=build/MUST_SET
|
|
||||||
CSV_BASENAME=timing4i.csv
|
|
||||||
|
|
||||||
all: $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/qor.txt
|
|
||||||
|
|
||||||
run:
|
|
||||||
$(MAKE) clean
|
|
||||||
$(MAKE) all
|
|
||||||
touch run.ok
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(BUILD_DIR)/$(CORNER)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER):
|
|
||||||
mkdir -p $(BUILD_DIR)/$(CORNER)
|
|
||||||
|
|
||||||
# parent should have built this
|
|
||||||
$(BUILD_DIR)/checksub:
|
|
||||||
false
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER)/leastsq.csv: $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv $(BUILD_DIR)/checksub $(BUILD_DIR)/$(CORNER)
|
|
||||||
# Create a rough timing model that approximately fits the given paths
|
|
||||||
python3 $(TIMFUZ_DIR)/solve_leastsq.py $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/leastsq.csv.tmp
|
|
||||||
mv $(BUILD_DIR)/$(CORNER)/leastsq.csv.tmp $(BUILD_DIR)/$(CORNER)/leastsq.csv
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER)/linprog.csv: $(BUILD_DIR)/$(CORNER)/leastsq.csv $(BUILD_DIR)/grouped.csv
|
|
||||||
# Tweak rough timing model, making sure all constraints are satisfied
|
|
||||||
ALLOW_ZERO_EQN=$(ALLOW_ZERO_EQN) python3 $(TIMFUZ_DIR)/solve_linprog.py --bounds-csv $(BUILD_DIR)/$(CORNER)/leastsq.csv --massage $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp
|
|
||||||
mv $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp $(BUILD_DIR)/$(CORNER)/linprog.csv
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER)/flat.csv: $(BUILD_DIR)/$(CORNER)/linprog.csv
|
|
||||||
# Take separated variables and back-annotate them to the original timing variables
|
|
||||||
python3 $(TIMFUZ_DIR)/csv_group2flat.py --sub-json $(BUILD_DIR)/sub.json --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/flat.csv.tmp $(BUILD_DIR)/$(CORNER)/linprog.csv
|
|
||||||
mv $(BUILD_DIR)/$(CORNER)/flat.csv.tmp $(BUILD_DIR)/$(CORNER)/flat.csv
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER)/timgrid-vc.json: $(BUILD_DIR)/$(CORNER)/flat.csv
|
|
||||||
# Final processing
|
|
||||||
# Insert timing delays into actual tile layouts
|
|
||||||
python3 $(TIMFUZ_DIR)/tile_annotate.py --timgrid-s $(TIMFUZ_DIR)/timgrid/build/timgrid-s.json --out $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/flat.csv
|
|
||||||
|
|
||||||
$(BUILD_DIR)/$(CORNER)/qor.txt: $(BUILD_DIR)/$(CORNER)/flat.csv
|
|
||||||
ifeq ($(SOLVING),i)
|
|
||||||
python3 $(TIMFUZ_DIR)/solve_qor.py --corner $(CORNER) --bounds-csv $(BUILD_DIR)/$(CORNER)/flat.csv specimen_*/timing4i.csv >$(BUILD_DIR)/$(CORNER)/qor.txt.tmp
|
|
||||||
mv $(BUILD_DIR)/$(CORNER)/qor.txt.tmp $(BUILD_DIR)/$(CORNER)/qor.txt
|
|
||||||
else
|
|
||||||
# FIXME
|
|
||||||
touch $(BUILD_DIR)/$(CORNER)/qor.txt
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source ${XRAY_GENHEADER}
|
|
||||||
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
|
|
||||||
|
|
||||||
timing_txt2csv () {
|
|
||||||
python3 $TIMFUZ_DIR/timing_txt2icsv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4i.csv.tmp timing4.txt
|
|
||||||
mv timing4i.csv.tmp timing4i.csv
|
|
||||||
|
|
||||||
python3 $TIMFUZ_DIR/timing_txt2scsv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4s.csv.tmp timing4.txt
|
|
||||||
mv timing4s.csv.tmp timing4s.csv
|
|
||||||
|
|
||||||
# delete really large file, see https://github.com/SymbiFlow/prjxray/issues/137
|
|
||||||
rm timing4.txt
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
# Interconnect and site (IS) high level aggregation
|
|
||||||
# Creates corner data and aggregates them together
|
|
||||||
|
|
||||||
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
|
|
||||||
SOLVING=i
|
|
||||||
CSV_BASENAME=timing4$(SOLVING).csv
|
|
||||||
BUILD_DIR?=build/MUST_SET
|
|
||||||
SPECIMENS :=
|
|
||||||
CSVS := $(addsuffix /$(CSV_BASENAME),$(SPECIMENS))
|
|
||||||
RREF_CORNER=slow_max
|
|
||||||
|
|
||||||
# Set ZERO elements to zero delay (as is expected they should be)
|
|
||||||
RMZERO?=N
|
|
||||||
|
|
||||||
RREF_ARGS=
|
|
||||||
ifeq ($(RMZERO),Y)
|
|
||||||
RREF_ARGS+=--rm-zero
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
# FIXME: clean this up by generating targets from CORNERS
|
|
||||||
|
|
||||||
# fast_max => build/i/fast_max/timgrid-vc.json
|
|
||||||
TIMGRID_VCS=$(BUILD_DIR)/fast_max/timgrid-vc.json $(BUILD_DIR)/fast_min/timgrid-vc.json $(BUILD_DIR)/slow_max/timgrid-vc.json $(BUILD_DIR)/slow_min/timgrid-vc.json
|
|
||||||
#TIMGRID_VCS=$(addsuffix /timgrid-vc.json,$(addprefix $(BUILD_DIR_I)/,$(CORNERS)))
|
|
||||||
# make $(BUILD_DIR)/checksub first
|
|
||||||
$(BUILD_DIR)/fast_max/timgrid-vc.json: $(BUILD_DIR)/checksub
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_max
|
|
||||||
$(BUILD_DIR)/fast_min/timgrid-vc.json: $(BUILD_DIR)/checksub
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=fast_min
|
|
||||||
$(BUILD_DIR)/slow_max/timgrid-vc.json: $(BUILD_DIR)/checksub
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_max
|
|
||||||
$(BUILD_DIR)/slow_min/timgrid-vc.json: $(BUILD_DIR)/checksub
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/corner.mk CORNER=slow_min
|
|
||||||
|
|
||||||
|
|
||||||
# Normally require all projects to complete
|
|
||||||
# If BADPRJ_OK is allowed, only take projects that were successful
|
|
||||||
# FIXME: couldn't get call to work
|
|
||||||
exist_csvs = \
|
|
||||||
for f in $(CSVS); do \
|
|
||||||
if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then \
|
|
||||||
echo $$f; \
|
|
||||||
fi; \
|
|
||||||
done
|
|
||||||
|
|
||||||
all: $(BUILD_DIR)/timgrid-v.json
|
|
||||||
|
|
||||||
# rref should be the same regardless of corner
|
|
||||||
$(BUILD_DIR)/sub.json: $(SPECIMENS_OK)
|
|
||||||
mkdir -p $(BUILD_DIR)
|
|
||||||
# Discover which variables can be separated
|
|
||||||
# This is typically the longest running operation
|
|
||||||
\
|
|
||||||
csvs=$$(for f in $(CSVS); do if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then echo $$f; fi; done) ; \
|
|
||||||
echo $$csvs ; \
|
|
||||||
python3 $(TIMFUZ_DIR)/rref.py --corner $(RREF_CORNER) --simplify $(RREF_ARGS) --out $(BUILD_DIR)/sub.json.tmp $$csvs
|
|
||||||
mv $(BUILD_DIR)/sub.json.tmp $(BUILD_DIR)/sub.json
|
|
||||||
|
|
||||||
$(BUILD_DIR)/grouped.csv: $(SPECIMENS_OK) $(BUILD_DIR)/sub.json
|
|
||||||
# Separate variables
|
|
||||||
\
|
|
||||||
csvs=$$(for f in $(CSVS); do if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then echo $$f; fi; done) ; \
|
|
||||||
python3 $(TIMFUZ_DIR)/csv_flat2group.py --sub-json $(BUILD_DIR)/sub.json --strict --out $(BUILD_DIR)/grouped.csv.tmp $$csvs
|
|
||||||
mv $(BUILD_DIR)/grouped.csv.tmp $(BUILD_DIR)/grouped.csv
|
|
||||||
|
|
||||||
$(BUILD_DIR)/checksub: $(BUILD_DIR)/grouped.csv $(BUILD_DIR)/sub.json
|
|
||||||
# Verify sub.json makes a cleanly solvable solution with no non-pivot leftover
|
|
||||||
python3 $(TIMFUZ_DIR)/checksub.py --sub-json $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv
|
|
||||||
touch $(BUILD_DIR)/checksub
|
|
||||||
|
|
||||||
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_VCS)
|
|
||||||
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_VCS)
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
# so simple that likely no linprog adjustments will be needed
|
|
||||||
ALLOW_ZERO_EQN?=Y
|
|
||||||
include ../project.mk
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Minimal, simple test
|
|
||||||
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
source ../generate.sh
|
|
||||||
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
timing_txt2csv
|
|
||||||
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
source ../../../../../utils/utils.tcl
|
|
||||||
source ../../project.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog ../top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
#randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
write_info4
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
module roi (
|
|
||||||
input wire clk,
|
|
||||||
output wire out);
|
|
||||||
reg [23:0] counter;
|
|
||||||
assign out = counter[23] ^ counter[22] ^ counter[2] && counter[1] || counter[0];
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
counter <= counter + 1;
|
|
||||||
end
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module top(input wire clk, input wire stb, input wire di, output wire do);
|
|
||||||
localparam integer DIN_N = 0;
|
|
||||||
localparam integer DOUT_N = 1;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
roi roi(
|
|
||||||
.clk(clk),
|
|
||||||
.out(dout[0])
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
include ../project.mk
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
picorv32 uses more advanced structures than simple LUT/FF tests (such as carry chain)
|
|
||||||
It attempts to provide some realistic timing data wrt fanout and used fabric
|
|
||||||
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
source ../generate.sh
|
|
||||||
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
timing_txt2csv
|
|
||||||
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
source ../../../../../utils/utils.tcl
|
|
||||||
source ../../project.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog ../../../src/picorv32.v
|
|
||||||
read_verilog ../top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
write_info4
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
//move some stuff to minitests/ncy0
|
|
||||||
|
|
||||||
`define SEED 32'h12345678
|
|
||||||
|
|
||||||
module top(input clk, stb, di, output do);
|
|
||||||
localparam integer DIN_N = 42;
|
|
||||||
localparam integer DOUT_N = 79;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
|
|
||||||
roi #(.DIN_N(DIN_N), .DOUT_N(DOUT_N))
|
|
||||||
roi (
|
|
||||||
.clk(clk),
|
|
||||||
.din(din),
|
|
||||||
.dout(dout)
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module roi(input clk, input [DIN_N-1:0] din, output [DOUT_N-1:0] dout);
|
|
||||||
parameter integer DIN_N = -1;
|
|
||||||
parameter integer DOUT_N = -1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
//Take out for now to make sure LUTs are more predictable
|
|
||||||
picorv32 picorv32 (
|
|
||||||
.clk(clk),
|
|
||||||
.resetn(din[0]),
|
|
||||||
.mem_valid(dout[0]),
|
|
||||||
.mem_instr(dout[1]),
|
|
||||||
.mem_ready(din[1]),
|
|
||||||
.mem_addr(dout[33:2]),
|
|
||||||
.mem_wdata(dout[66:34]),
|
|
||||||
.mem_wstrb(dout[70:67]),
|
|
||||||
.mem_rdata(din[33:2])
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
randluts randluts (
|
|
||||||
.din(din[41:34]),
|
|
||||||
.dout(dout[78:71])
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
randluts #(.N(150)) randluts (
|
|
||||||
.din(din[41:34]),
|
|
||||||
.dout(dout[78:71])
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module randluts(input [7:0] din, output [7:0] dout);
|
|
||||||
parameter integer N = 250;
|
|
||||||
|
|
||||||
function [31:0] xorshift32(input [31:0] xorin);
|
|
||||||
begin
|
|
||||||
xorshift32 = xorin;
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 << 13);
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 >> 17);
|
|
||||||
xorshift32 = xorshift32 ^ (xorshift32 << 5);
|
|
||||||
end
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function [63:0] lutinit(input [7:0] a, b);
|
|
||||||
begin
|
|
||||||
lutinit[63:32] = xorshift32(xorshift32(xorshift32(xorshift32({a, b} ^ `SEED))));
|
|
||||||
lutinit[31: 0] = xorshift32(xorshift32(xorshift32(xorshift32({b, a} ^ `SEED))));
|
|
||||||
end
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
wire [(N+1)*8-1:0] nets;
|
|
||||||
|
|
||||||
assign nets[7:0] = din;
|
|
||||||
assign dout = nets[(N+1)*8-1:N*8];
|
|
||||||
|
|
||||||
genvar i, j;
|
|
||||||
generate
|
|
||||||
for (i = 0; i < N; i = i+1) begin:is
|
|
||||||
for (j = 0; j < 8; j = j+1) begin:js
|
|
||||||
localparam integer k = xorshift32(xorshift32(xorshift32(xorshift32((i << 20) ^ (j << 10) ^ `SEED)))) & 255;
|
|
||||||
(* KEEP, DONT_TOUCH *)
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(lutinit(i, j))
|
|
||||||
) lut (
|
|
||||||
.I0(nets[8*i+(k+0)%8]),
|
|
||||||
.I1(nets[8*i+(k+1)%8]),
|
|
||||||
.I2(nets[8*i+(k+2)%8]),
|
|
||||||
.I3(nets[8*i+(k+3)%8]),
|
|
||||||
.I4(nets[8*i+(k+4)%8]),
|
|
||||||
.I5(nets[8*i+(k+5)%8]),
|
|
||||||
.O(nets[8*i+8+j])
|
|
||||||
);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
endgenerate
|
|
||||||
endmodule
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
include ../project.mk
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
LUTs are physically laid out in an array and directly connected to a test pattern generator
|
|
||||||
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='')
|
|
||||||
parser.add_argument('--sdx', default='8', help='')
|
|
||||||
parser.add_argument('--sdy', default='4', help='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
'''
|
|
||||||
Generate in pairs
|
|
||||||
Fill up switchbox quad for now
|
|
||||||
Create random connections between the LUTs
|
|
||||||
See how much routing pressure we can generate
|
|
||||||
|
|
||||||
Start with non-random connections to the LFSR for solver comparison
|
|
||||||
|
|
||||||
Start at SLICE_X16Y102
|
|
||||||
'''
|
|
||||||
SBASE = (16, 102)
|
|
||||||
SDX = int(args.sdx, 0)
|
|
||||||
SDY = int(args.sdy, 0)
|
|
||||||
nlut = 4 * SDX * SDY
|
|
||||||
|
|
||||||
nin = 6 * nlut
|
|
||||||
nout = nlut
|
|
||||||
|
|
||||||
print('//placelut simple')
|
|
||||||
print('//SBASE: %s' % (SBASE, ))
|
|
||||||
print('//SDX: %s' % (SDX, ))
|
|
||||||
print('//SDY: %s' % (SDX, ))
|
|
||||||
print('//nlut: %s' % (nlut, ))
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
module roi (
|
|
||||||
input wire clk,
|
|
||||||
input wire [%u:0] ins,
|
|
||||||
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
|
|
||||||
|
|
||||||
ini = 0
|
|
||||||
outi = 0
|
|
||||||
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
|
|
||||||
for luty in xrange(SBASE[1], SBASE[1] + SDY):
|
|
||||||
loc = "SLICE_X%uY%u" % (lutx, luty)
|
|
||||||
for belc in 'ABCD':
|
|
||||||
bel = '%c6LUT' % belc
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
|
|
||||||
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(64'hBAD1DEA_1DEADCE0)
|
|
||||||
) %s (''') % (loc, bel, 'lut_x%uy%u_%c' % (lutx, luty, belc))
|
|
||||||
for i in xrange(6):
|
|
||||||
print('''\
|
|
||||||
.I%u(ins[%u]),''' % (i, ini))
|
|
||||||
ini += 1
|
|
||||||
print('''\
|
|
||||||
.O(outs[%u]));''') % (outi, )
|
|
||||||
outi += 1
|
|
||||||
assert nin == ini
|
|
||||||
assert nout == outi
|
|
||||||
|
|
||||||
print(
|
|
||||||
'''
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module top(input wire clk, input wire stb, input wire di, output wire do);
|
|
||||||
localparam integer DIN_N = %u;
|
|
||||||
localparam integer DOUT_N = %u;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
roi roi(
|
|
||||||
.clk(clk),
|
|
||||||
.ins(din),
|
|
||||||
.outs(dout)
|
|
||||||
);
|
|
||||||
endmodule''') % (nin, nout)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
source ${XRAY_GENHEADER}
|
|
||||||
TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
|
|
||||||
|
|
||||||
python3 ../generate.py --sdx 4 --sdy 4 >top.v
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4.csv timing4.txt
|
|
||||||
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
source ../../../../../utils/utils.tcl
|
|
||||||
source ../../project.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
write_info4
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
include ../project.mk
|
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
LUTs are physically laid out in an array and connected to test pattern generator and fed back to other LUTs
|
|
||||||
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
'''
|
|
||||||
Note: vivado will (by default) fail bitgen DRC on LUT feedback loops
|
|
||||||
Looks like can probably be disabled, but we actually don't need a bitstream for timing analysis
|
|
||||||
'''
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import random
|
|
||||||
|
|
||||||
random.seed()
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='')
|
|
||||||
parser.add_argument('--sdx', default='8', help='')
|
|
||||||
parser.add_argument('--sdy', default='4', help='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
'''
|
|
||||||
Generate in pairs
|
|
||||||
Fill up switchbox quad for now
|
|
||||||
Create random connections between the LUTs
|
|
||||||
See how much routing pressure we can generate
|
|
||||||
|
|
||||||
Start with non-random connections to the LFSR for solver comparison
|
|
||||||
|
|
||||||
Start at SLICE_X16Y102
|
|
||||||
'''
|
|
||||||
SBASE = (16, 102)
|
|
||||||
SDX = int(args.sdx, 0)
|
|
||||||
SDY = int(args.sdy, 0)
|
|
||||||
nlut = 4 * SDX * SDY
|
|
||||||
|
|
||||||
nin = 6 * nlut
|
|
||||||
nout = nlut
|
|
||||||
|
|
||||||
print('//placelut w/ feedback')
|
|
||||||
print('//SBASE: %s' % (SBASE, ))
|
|
||||||
print('//SDX: %s' % (SDX, ))
|
|
||||||
print('//SDY: %s' % (SDX, ))
|
|
||||||
print('//nlut: %s' % (nlut, ))
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
module roi (
|
|
||||||
input wire clk,
|
|
||||||
input wire [%u:0] ins,
|
|
||||||
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
|
|
||||||
|
|
||||||
ini = 0
|
|
||||||
outi = 0
|
|
||||||
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
|
|
||||||
for luty in xrange(SBASE[1], SBASE[1] + SDY):
|
|
||||||
loc = "SLICE_X%uY%u" % (lutx, luty)
|
|
||||||
for belc in 'ABCD':
|
|
||||||
bel = '%c6LUT' % belc
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
|
|
||||||
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(64'hBAD1DEA_1DEADCE0)
|
|
||||||
) %s (''') % (loc, bel, 'lut_x%uy%u_%c' % (lutx, luty, belc))
|
|
||||||
for i in xrange(6):
|
|
||||||
if random.randint(0, 9) < 1:
|
|
||||||
wfrom = 'ins[%u]' % ini
|
|
||||||
ini += 1
|
|
||||||
else:
|
|
||||||
wfrom = 'outs[%u]' % random.randint(0, nout - 1)
|
|
||||||
print('''\
|
|
||||||
.I%u(%s),''' % (i, wfrom))
|
|
||||||
print('''\
|
|
||||||
.O(outs[%u]));''') % (outi, )
|
|
||||||
outi += 1
|
|
||||||
#assert nin == ini
|
|
||||||
assert nout == outi
|
|
||||||
|
|
||||||
print(
|
|
||||||
'''
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module top(input wire clk, input wire stb, input wire di, output wire do);
|
|
||||||
localparam integer DIN_N = %u;
|
|
||||||
localparam integer DOUT_N = %u;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
roi roi(
|
|
||||||
.clk(clk),
|
|
||||||
.ins(din),
|
|
||||||
.outs(dout)
|
|
||||||
);
|
|
||||||
endmodule''') % (nin, nout)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
source ../generate.sh
|
|
||||||
|
|
||||||
python3 ../generate.py --sdx 4 --sdy 4 >top.v
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
timing_txt2csv
|
|
||||||
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
source ../../../../../utils/utils.tcl
|
|
||||||
source ../../project.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
write_info4
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
include ../project.mk
|
|
||||||
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
LUTs are physically laid out in an array and connected to test pattern generator and fed back to other LUTs.
|
|
||||||
FFs are randomly inserted between connections.
|
|
||||||
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
'''
|
|
||||||
Note: vivado will (by default) fail bitgen DRC on LUT feedback loops
|
|
||||||
Looks like can probably be disabled, but we actually don't need a bitstream for timing analysis
|
|
||||||
|
|
||||||
ERROR: [Vivado 12-2285] Cannot set LOC property of instance 'roi/lut_x22y102_D', Instance roi/lut_x22y102_D can not be placed in D6LUT of site SLICE_X18Y103 because the bel is occupied by roi/lut_x18y103_D(port:). This could be caused by bel constraint conflict
|
|
||||||
Resolution: When using BEL constraints, ensure the BEL constraints are defined before the LOC constraints to avoid conflicts at a given site.
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import random
|
|
||||||
|
|
||||||
random.seed()
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='')
|
|
||||||
parser.add_argument('--sdx', default='8', help='')
|
|
||||||
parser.add_argument('--sdy', default='4', help='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
'''
|
|
||||||
Generate in pairs
|
|
||||||
Fill up switchbox quad for now
|
|
||||||
Create random connections between the LUTs
|
|
||||||
See how much routing pressure we can generate
|
|
||||||
|
|
||||||
Start with non-random connections to the LFSR for solver comparison
|
|
||||||
|
|
||||||
Start at SLICE_X16Y102
|
|
||||||
'''
|
|
||||||
SBASE = (16, 102)
|
|
||||||
SDX = int(args.sdx, 0)
|
|
||||||
SDY = int(args.sdy, 0)
|
|
||||||
nlut = 4 * SDX * SDY
|
|
||||||
|
|
||||||
nin = 6 * nlut
|
|
||||||
nout = nlut
|
|
||||||
|
|
||||||
print('//placelut w/ FF + feedback')
|
|
||||||
print('//SBASE: %s' % (SBASE, ))
|
|
||||||
print('//SDX: %s' % (SDX, ))
|
|
||||||
print('//SDY: %s' % (SDX, ))
|
|
||||||
print('//nlut: %s' % (nlut, ))
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
module roi (
|
|
||||||
input wire clk,
|
|
||||||
input wire [%u:0] ins,
|
|
||||||
output wire [%u:0] outs);''') % (nin - 1, nout - 1)
|
|
||||||
|
|
||||||
ini = 0
|
|
||||||
outi = 0
|
|
||||||
for lutx in xrange(SBASE[0], SBASE[0] + SDX):
|
|
||||||
for luty in xrange(SBASE[1], SBASE[1] + SDY):
|
|
||||||
loc = "SLICE_X%uY%u" % (lutx, luty)
|
|
||||||
for belc in 'ABCD':
|
|
||||||
bel = '%c6LUT' % belc
|
|
||||||
name = 'lut_x%uy%u_%c' % (lutx, luty, belc)
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
|
|
||||||
(* KEEP, DONT_TOUCH, LOC="%s", BEL="%s" *)
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(64'hBAD1DEA_1DEADCE0)
|
|
||||||
) %s (''') % (loc, bel, name)
|
|
||||||
for i in xrange(6):
|
|
||||||
rval = random.randint(0, 9)
|
|
||||||
if rval < 3:
|
|
||||||
wfrom = 'ins[%u]' % ini
|
|
||||||
ini += 1
|
|
||||||
#elif rval < 6:
|
|
||||||
# wfrom = 'outsr[%u]' % random.randint(0, nout - 1)
|
|
||||||
else:
|
|
||||||
wfrom = 'outs[%u]' % random.randint(0, nout - 1)
|
|
||||||
print('''\
|
|
||||||
.I%u(%s),''' % (i, wfrom))
|
|
||||||
out_w = name + '_o'
|
|
||||||
print('''\
|
|
||||||
.O(%s));''') % (out_w, )
|
|
||||||
|
|
||||||
outs_w = "outs[%u]" % outi
|
|
||||||
if random.randint(0, 9) < 5:
|
|
||||||
print(' assign %s = %s;' % (outs_w, out_w))
|
|
||||||
else:
|
|
||||||
out_r = name + '_or'
|
|
||||||
print(
|
|
||||||
'''\
|
|
||||||
reg %s;
|
|
||||||
assign %s = %s;
|
|
||||||
always @(posedge clk) begin
|
|
||||||
%s = %s;
|
|
||||||
end
|
|
||||||
''' % (out_r, outs_w, out_r, out_r, out_w))
|
|
||||||
outi += 1
|
|
||||||
|
|
||||||
#assert nin == ini
|
|
||||||
assert nout == outi
|
|
||||||
|
|
||||||
print(
|
|
||||||
'''
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module top(input wire clk, input wire stb, input wire di, output wire do);
|
|
||||||
localparam integer DIN_N = %u;
|
|
||||||
localparam integer DOUT_N = %u;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
roi roi(
|
|
||||||
.clk(clk),
|
|
||||||
.ins(din),
|
|
||||||
.outs(dout)
|
|
||||||
);
|
|
||||||
endmodule''') % (nin, nout)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
source ../generate.sh
|
|
||||||
|
|
||||||
python3 ../generate.py --sdx 4 --sdy 4 >top.v
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
timing_txt2csv
|
|
||||||
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
source ../../../../../utils/utils.tcl
|
|
||||||
source ../../project.tcl
|
|
||||||
|
|
||||||
proc build_design {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
puts "Locking pins"
|
|
||||||
set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
[get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
puts "Package stuff"
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
puts "pblocking"
|
|
||||||
create_pblock roi
|
|
||||||
set roipb [get_pblocks roi]
|
|
||||||
add_cells_to_pblock $roipb [get_cells roi]
|
|
||||||
resize_pblock $roipb -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
puts "randplace"
|
|
||||||
randplace_pblock 50 roi
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
puts "dedicated route"
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
# disable combinitorial loop
|
|
||||||
# set_property IS_ENABLED 0 [get_drc_checks {LUTLP-1}]
|
|
||||||
#write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design
|
|
||||||
write_info4
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# project.mk: build specimens (run vivado), compute rref
|
|
||||||
# corner.mk: run corner specific calculations
|
|
||||||
|
|
||||||
N := 1
|
|
||||||
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
|
|
||||||
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
|
|
||||||
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
|
|
||||||
# Allow an empty system of equations?
|
|
||||||
# for testing only on small projects
|
|
||||||
ALLOW_ZERO_EQN?=N
|
|
||||||
# Constrained projects may fail to build
|
|
||||||
# Set to Y to make a best effort to suck in the ones that did build
|
|
||||||
BADPRJ_OK?=N
|
|
||||||
BUILD_DIR?=build
|
|
||||||
# interconnect
|
|
||||||
BUILD_DIR_I?=$(BUILD_DIR)/i
|
|
||||||
# site
|
|
||||||
BUILD_DIR_S?=$(BUILD_DIR)/s
|
|
||||||
|
|
||||||
CORNERS=fast_max fast_min slow_max slow_min
|
|
||||||
|
|
||||||
TIMGRID_V_I=$(BUILD_DIR_I)/timgrid-v.json
|
|
||||||
TIMGRID_V_S=$(BUILD_DIR_S)/timgrid-v.json
|
|
||||||
|
|
||||||
all: $(BUILD_DIR)/timgrid-v.json
|
|
||||||
|
|
||||||
$(SPECIMENS_OK):
|
|
||||||
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
|
|
||||||
touch $@
|
|
||||||
|
|
||||||
run:
|
|
||||||
$(MAKE) clean
|
|
||||||
$(MAKE) all
|
|
||||||
touch run.ok
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf specimen_[0-9][0-9][0-9]/ seg_clblx.segbits __pycache__ run.ok
|
|
||||||
rm -rf vivado*.log vivado_*.str vivado*.jou design *.bits *.dcp *.bit
|
|
||||||
rm -rf $(BUILD_DIR)
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
|
|
||||||
$(TIMGRID_V_I): $(SPECIMENS_OK)
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_I) SOLVING=i SPECIMENS=$(SPECIMENS) all
|
|
||||||
i: $(TIMGRID_V_I)
|
|
||||||
|
|
||||||
$(TIMGRID_V_S): $(SPECIMENS_OK)
|
|
||||||
$(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_S) SOLVING=s SPECIMENS=$(SPECIMENS) all
|
|
||||||
s: $(TIMGRID_V_S)
|
|
||||||
|
|
||||||
.PHONY: i s
|
|
||||||
|
|
||||||
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_V_I) $(TIMGRID_V_S)
|
|
||||||
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_V_I) $(TIMGRID_V_S)
|
|
||||||
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
# General notes:
|
|
||||||
# Observed nets with 1 to 2 source cell pins
|
|
||||||
# 2 example
|
|
||||||
# [get_pins -filter {DIRECTION == OUT} -of_objects $net]
|
|
||||||
# roi/dout_shr[0]_i_1/O: BEL, visible
|
|
||||||
# roi/dout_shr_reg[0]: no BEL, not visible, named after the net
|
|
||||||
# I don't understand what the relationship between these two is
|
|
||||||
# Maybe register retiming?
|
|
||||||
# Observed nets with 0 to 1 source bel pins
|
|
||||||
# 0 example: <const0>
|
|
||||||
# Recommendation: assume there is at most one source BEL
|
|
||||||
# Take that as the source BEL if it exists, otherwise don't do any source analysis
|
|
||||||
|
|
||||||
# get_timing_paths vs get_net_delays
|
|
||||||
# get_timing_paths is built on top of get_net_delays?
|
|
||||||
# has some different info, but is fixed on the slow max corner
|
|
||||||
# *possibly* reports slightly better info within a site, but would need to verify that
|
|
||||||
|
|
||||||
# Things safe to assume:
|
|
||||||
# -Each net has at least one delay?
|
|
||||||
# -Cell exists for every delay path
|
|
||||||
# -Each net has at least one destination BEL pin
|
|
||||||
# Things not safe to assume
|
|
||||||
# -Each net has at least one source BEL pin (ex: <const0>)
|
|
||||||
|
|
||||||
|
|
||||||
proc list_format {l delim} {
|
|
||||||
set ret ""
|
|
||||||
set needspace 0
|
|
||||||
foreach x $l {
|
|
||||||
if $needspace {
|
|
||||||
set ret "$ret$delim$x"
|
|
||||||
} else {
|
|
||||||
set ret "$x"
|
|
||||||
}
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
return $ret
|
|
||||||
}
|
|
||||||
|
|
||||||
proc line_net_external {fp net src_site src_site_type src_site_pin src_bel src_bel_pin dst_site dst_site_type dst_site_pin dst_bel dst_bel_pin ico fast_max fast_min slow_max slow_min pips_out wires_out} {
|
|
||||||
puts $fp "NET,$net,$src_site,$src_site_type,$src_site_pin,$src_bel,$src_bel_pin,$dst_site,$dst_site_type,$dst_site_pin,$dst_bel,$dst_bel_pin,$ico,$fast_max,$fast_min,$slow_max,$slow_min,$pips_out,$wires_out"
|
|
||||||
}
|
|
||||||
|
|
||||||
proc line_net_internal {fp net site site_type src_bel src_bel_pin dst_bel dst_bel_pin ico fast_max fast_min slow_max slow_min} {
|
|
||||||
set src_site $site
|
|
||||||
set src_site_type $site_type
|
|
||||||
set src_site_pin ""
|
|
||||||
|
|
||||||
set dst_site $site
|
|
||||||
set dst_site_type $site_type
|
|
||||||
set dst_site_pin ""
|
|
||||||
|
|
||||||
set pips_out ""
|
|
||||||
set wires_out ""
|
|
||||||
|
|
||||||
puts $fp "NET,$net,$src_site,$src_site_type,$src_site_pin,$src_bel,$src_bel_pin,$dst_site,$dst_site_type,$dst_site_pin,$dst_bel,$dst_bel_pin,$ico,$fast_max,$fast_min,$slow_max,$slow_min,$pips_out,$wires_out"
|
|
||||||
}
|
|
||||||
|
|
||||||
proc write_info4 {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/timing4.txt" w]
|
|
||||||
# bel as site/bel, so don't bother with site
|
|
||||||
puts $fp "linetype,net,src_site,src_site_type,src_site_pin,src_bel,src_bel_pin,dst_site,dst_site_type,dst_site_pin,dst_bel,dst_bel_pin,ico,fast_max,fast_min,slow_max,slow_min,pips,wires"
|
|
||||||
|
|
||||||
set TIME_start [clock clicks -milliseconds]
|
|
||||||
set delays_net 0
|
|
||||||
set nets_no_src_cell 0
|
|
||||||
set nets_no_src_bel 0
|
|
||||||
set lines_no_int 0
|
|
||||||
set lines_some_int 0
|
|
||||||
set neti 0
|
|
||||||
set nets [get_nets -hierarchical]
|
|
||||||
#set nets [get_nets clk]
|
|
||||||
#set nets [get_nets roi/counter[0]_i_2_n_0]
|
|
||||||
set nnets [llength $nets]
|
|
||||||
foreach net $nets {
|
|
||||||
incr neti
|
|
||||||
|
|
||||||
puts "Net $neti / $nnets: $net"
|
|
||||||
|
|
||||||
# there are some special places like on IOB where you may not have a cell source pin
|
|
||||||
# this is due to special treatment where BEL vs SITE are blurred
|
|
||||||
# The semantics of get_pins -leaf is kind of odd
|
|
||||||
# When no passthrough LUTs exist, it has no effect
|
|
||||||
# When passthrough LUT present:
|
|
||||||
# -w/o -leaf: some pins + passthrough LUT pins
|
|
||||||
# -w/ -leaf: different pins + passthrough LUT pins
|
|
||||||
# With OUT filter this seems to be sufficient
|
|
||||||
set src_cell_pins [get_pins -leaf -filter {DIRECTION == OUT} -of_objects $net]
|
|
||||||
if {$src_cell_pins eq ""} {
|
|
||||||
incr nets_no_src_cell
|
|
||||||
# ex: IOB internal bel net
|
|
||||||
puts " SKIP: no source cell"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
# 0 to 2 of these
|
|
||||||
# Seems when 2 of them they basically have the same bel + cell
|
|
||||||
# Make a best effort and move forward
|
|
||||||
set src_cell_pin [lindex $src_cell_pins 0]
|
|
||||||
set src_cell [get_cells -of_objects $src_cell_pin]
|
|
||||||
|
|
||||||
# Only applicable if in a site
|
|
||||||
set src_bel [get_bels -of_objects $src_cell]
|
|
||||||
if {$src_bel eq ""} {
|
|
||||||
# just throw out cases where source site doesn't exist
|
|
||||||
# these are very special, don't really have timing info anyway
|
|
||||||
# rework these later if they become problematic
|
|
||||||
incr nets_no_src_bel
|
|
||||||
puts " SKIP: no source bel"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
set src_bel_pin [get_bel_pins -of_objects $src_cell_pin]
|
|
||||||
set src_site [get_sites -of_objects $src_bel]
|
|
||||||
set src_site_type [get_property SITE_TYPE $src_site]
|
|
||||||
# optional
|
|
||||||
set src_site_pin [get_site_pins -of_objects $src_cell_pin]
|
|
||||||
|
|
||||||
# Report delays with and without interconnect only
|
|
||||||
foreach ico "0 1" {
|
|
||||||
if $ico {
|
|
||||||
set delays [get_net_delays -interconnect_only -of_objects $net]
|
|
||||||
} else {
|
|
||||||
set delays [get_net_delays -of_objects $net]
|
|
||||||
}
|
|
||||||
set delays_net [expr "$delays_net + [llength $delays]"]
|
|
||||||
puts $fp "GROUP,$ico,[llength $delays]"
|
|
||||||
foreach delay $delays {
|
|
||||||
#set delaystr [get_property NAME $delay]
|
|
||||||
|
|
||||||
set fast_max [get_property "FAST_MAX" $delay]
|
|
||||||
set fast_min [get_property "FAST_MIN" $delay]
|
|
||||||
set slow_max [get_property "SLOW_MAX" $delay]
|
|
||||||
set slow_min [get_property "SLOW_MIN" $delay]
|
|
||||||
|
|
||||||
# Does this always exist?
|
|
||||||
set dst_cell_pin_str [get_property TO_PIN $delay]
|
|
||||||
set dst_cell_pin [get_pins $dst_cell_pin_str]
|
|
||||||
set dst_cell [get_cells -of_objects $dst_cell_pin]
|
|
||||||
set dst_bel [get_bels -of_objects $dst_cell]
|
|
||||||
# WARNING: when there is a passthrough LUT, this can be multiple values
|
|
||||||
# (one for the real dest pin, and one for the passthrough lut input)
|
|
||||||
set dst_bel_pin [get_bel_pins -of_objects $dst_cell_pin]
|
|
||||||
set dst_site [get_sites -of_objects $dst_bel]
|
|
||||||
set dst_site_type [get_property SITE_TYPE $dst_site]
|
|
||||||
# optional
|
|
||||||
set dst_site_pin [get_site_pins -of_objects $dst_cell_pin]
|
|
||||||
|
|
||||||
# No fabric on this net?
|
|
||||||
# don't try querying sites and such
|
|
||||||
if {[get_nodes -of_objects $net] eq ""} {
|
|
||||||
if {$src_site ne $dst_site} {
|
|
||||||
puts "ERROR: site mismatch"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if {$src_site_type ne $dst_site_type} {
|
|
||||||
puts "ERROR: site mismatch"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
# Already have everything we could query
|
|
||||||
# Just output
|
|
||||||
incr lines_no_int
|
|
||||||
line_net_internal $fp $net $src_site $src_site_type $src_bel $src_bel_pin $dst_bel $dst_bel_pin $ico $fast_max $fast_min $slow_max $slow_min
|
|
||||||
# At least some fabric exists
|
|
||||||
# Does dest BEL exist but not source BEL?
|
|
||||||
} elseif {$src_bel eq ""} {
|
|
||||||
puts "ERROR: should have been filtered"
|
|
||||||
return
|
|
||||||
# Ideally query from and to cell pins
|
|
||||||
} else {
|
|
||||||
# Nested list delimination precedence: ",|:"
|
|
||||||
|
|
||||||
# Pips in between
|
|
||||||
# Walk net, looking for interesting elements in between
|
|
||||||
# -from <args> - (Optional) Defines the starting points of the pips to get
|
|
||||||
# from wire or site_pin
|
|
||||||
set pips [get_pips -of_objects $net -from $src_site_pin -to $dst_site_pin]
|
|
||||||
# Write pips w/ speed index
|
|
||||||
foreach pip $pips {
|
|
||||||
set speed_index [get_property SPEED_INDEX $pip]
|
|
||||||
lappend pips_out "$pip:$speed_index"
|
|
||||||
}
|
|
||||||
set pips_out [list_format "$pips_out" "|"]
|
|
||||||
|
|
||||||
set nodes [get_nodes -of_objects $net -from $src_site_pin -to $dst_site_pin]
|
|
||||||
if {$nodes eq ""} {
|
|
||||||
puts "ERROR: no nodes"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
set wires [get_wires -of_objects $nodes]
|
|
||||||
# Write wires
|
|
||||||
foreach wire $wires {
|
|
||||||
set speed_index [get_property SPEED_INDEX $wire]
|
|
||||||
lappend wires_out "$wire:$speed_index"
|
|
||||||
}
|
|
||||||
set wires_out [list_format "$wires_out" "|"]
|
|
||||||
|
|
||||||
incr lines_some_int
|
|
||||||
line_net_external $fp $net $src_site $src_site_type $src_site_pin $src_bel $src_bel_pin $dst_site $dst_site_type $dst_site_pin $dst_bel $dst_bel_pin $ico $fast_max $fast_min $slow_max $slow_min $pips_out $wires_out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
|
|
||||||
puts "Took ms: $TIME_taken"
|
|
||||||
puts "Checked $delays_net delays"
|
|
||||||
puts "Nets: $nnets"
|
|
||||||
puts " Skipped (no source cell): $nets_no_src_cell"
|
|
||||||
puts " Skipped (no source bel): $nets_no_src_bel"
|
|
||||||
puts "Lines"
|
|
||||||
puts " No interconnect: $lines_no_int"
|
|
||||||
puts " Has interconnect: $lines_some_int"
|
|
||||||
}
|
|
||||||
|
|
||||||
# for debugging
|
|
||||||
# source ../project.tcl
|
|
||||||
# write_info4
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, Ar_di2np, loadc_Ads_b, index_names, A_ds2np, simplify_rows, OrderedSet
|
|
||||||
import numpy as np
|
|
||||||
import glob
|
|
||||||
import math
|
|
||||||
import json
|
|
||||||
import sympy
|
|
||||||
from collections import OrderedDict
|
|
||||||
from fractions import Fraction
|
|
||||||
|
|
||||||
|
|
||||||
def rm_zero_cols(Ads, verbose=True):
|
|
||||||
removed = OrderedSet()
|
|
||||||
|
|
||||||
print('Removing ZERO elements')
|
|
||||||
for row_ds in Ads:
|
|
||||||
for k in set(row_ds.keys()):
|
|
||||||
if k in removed:
|
|
||||||
del row_ds[k]
|
|
||||||
elif k.find('ZERO') >= 0:
|
|
||||||
del row_ds[k]
|
|
||||||
removed.add(k)
|
|
||||||
if verbose:
|
|
||||||
print(' Removing %s' % k)
|
|
||||||
return removed
|
|
||||||
|
|
||||||
|
|
||||||
def fracr_quick(r):
|
|
||||||
return [Fraction(numerator=int(x), denominator=1) for x in r]
|
|
||||||
|
|
||||||
|
|
||||||
def fracm_quick(m):
|
|
||||||
'''Convert integer matrix to Fraction matrix'''
|
|
||||||
t = type(m[0][0])
|
|
||||||
print('fracm_quick type: %s' % t)
|
|
||||||
return [fracr_quick(r) for r in m]
|
|
||||||
|
|
||||||
|
|
||||||
class State(object):
|
|
||||||
def __init__(self, Ads, zero_names=[]):
|
|
||||||
self.Ads = Ads
|
|
||||||
self.names = index_names(self.Ads)
|
|
||||||
|
|
||||||
# known zero delay elements
|
|
||||||
self.zero_names = OrderedSet(zero_names)
|
|
||||||
# active names in rows
|
|
||||||
# includes sub variables, excludes variables that have been substituted out
|
|
||||||
self.base_names = OrderedSet(self.names)
|
|
||||||
#self.names = OrderedSet(self.base_names)
|
|
||||||
self.names = set(self.base_names)
|
|
||||||
# List of variable substitutions
|
|
||||||
# k => dict of v:n entries that it came from
|
|
||||||
self.subs = OrderedDict()
|
|
||||||
self.verbose = True
|
|
||||||
|
|
||||||
def print_stats(self):
|
|
||||||
print("Stats")
|
|
||||||
print(" Substitutions: %u" % len(self.subs))
|
|
||||||
if self.subs:
|
|
||||||
print(
|
|
||||||
" Largest: %u" % max([len(x) for x in self.subs.values()]))
|
|
||||||
print(" Rows: %u" % len(self.Ads))
|
|
||||||
print(
|
|
||||||
" Cols (in): %u" % (len(self.base_names) + len(self.zero_names)))
|
|
||||||
print(" Cols (preprocessed): %u" % len(self.base_names))
|
|
||||||
print(" ZERO names: %u" % len(self.zero_names))
|
|
||||||
print(" Cols (out): %u" % len(self.names))
|
|
||||||
print(" Solvable vars: %u" % len(self.names & self.base_names))
|
|
||||||
assert len(self.names) >= len(self.subs)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def load(fn_ins, simplify=False, corner=None, rm_zero=False):
|
|
||||||
zero_names = OrderedSet()
|
|
||||||
|
|
||||||
Ads, b = loadc_Ads_b(fn_ins, corner=corner)
|
|
||||||
if rm_zero:
|
|
||||||
zero_names = rm_zero_cols(Ads)
|
|
||||||
if simplify:
|
|
||||||
print('Simplifying corner %s' % (corner, ))
|
|
||||||
Ads, b = simplify_rows(Ads, b, remove_zd=False, corner=corner)
|
|
||||||
return State(Ads, zero_names=zero_names)
|
|
||||||
|
|
||||||
|
|
||||||
def write_state(state, fout):
|
|
||||||
j = {
|
|
||||||
'names':
|
|
||||||
OrderedDict([(x, None) for x in state.names]),
|
|
||||||
'zero_names':
|
|
||||||
sorted(list(state.zero_names)),
|
|
||||||
'base_names':
|
|
||||||
sorted(list(state.base_names)),
|
|
||||||
'subs':
|
|
||||||
OrderedDict([(name, values) for name, values in state.subs.items()]),
|
|
||||||
'pivots':
|
|
||||||
state.pivots,
|
|
||||||
}
|
|
||||||
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
def row_np2ds(rownp, names):
|
|
||||||
ret = OrderedDict()
|
|
||||||
assert len(rownp) == len(names), (len(rownp), len(names))
|
|
||||||
for namei, name in enumerate(names):
|
|
||||||
v = rownp[namei]
|
|
||||||
if v:
|
|
||||||
ret[name] = v
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def row_sym2dsf(rowsym, names):
|
|
||||||
'''Convert a sympy row into a dictionary of keys to (numerator, denominator) tuples'''
|
|
||||||
from sympy import fraction
|
|
||||||
|
|
||||||
ret = OrderedDict()
|
|
||||||
assert len(rowsym) == len(names), (len(rowsym), len(names))
|
|
||||||
for namei, name in enumerate(names):
|
|
||||||
v = rowsym[namei]
|
|
||||||
if v:
|
|
||||||
(num, den) = fraction(v)
|
|
||||||
ret[name] = (int(num), int(den))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def state_rref(state, verbose=False):
|
|
||||||
print('Converting rows to integer keys')
|
|
||||||
names, Anp = A_ds2np(state.Ads)
|
|
||||||
|
|
||||||
print('np: %u rows x %u cols' % (len(Anp), len(Anp[0])))
|
|
||||||
mnp = Anp
|
|
||||||
print('Matrix: %u rows x %u cols' % (len(mnp), len(mnp[0])))
|
|
||||||
print('Converting np to sympy matrix')
|
|
||||||
mfrac = fracm_quick(mnp)
|
|
||||||
# doesn't seem to change anything
|
|
||||||
#msym = sympy.MutableSparseMatrix(mfrac)
|
|
||||||
msym = sympy.Matrix(mfrac)
|
|
||||||
# internal encoding has significnat performance implications
|
|
||||||
#assert type(msym[0]) is sympy.Integer
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('names')
|
|
||||||
print(names)
|
|
||||||
print('Matrix')
|
|
||||||
sympy.pprint(msym)
|
|
||||||
print('Making rref')
|
|
||||||
rref, pivots = msym.rref(normalize_last=False)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('Pivots')
|
|
||||||
sympy.pprint(pivots)
|
|
||||||
print('rref')
|
|
||||||
sympy.pprint(rref)
|
|
||||||
|
|
||||||
state.pivots = OrderedDict()
|
|
||||||
|
|
||||||
def row_solved(rowsym, row_pivot):
|
|
||||||
for ci, c in enumerate(rowsym):
|
|
||||||
if ci == row_pivot:
|
|
||||||
continue
|
|
||||||
if c != 0:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
#rrefnp = np.array(rref).astype(np.float64)
|
|
||||||
#print('Computing groups w/ rref %u row x %u col' % (len(rrefnp), len(rrefnp[0])))
|
|
||||||
#print(rrefnp)
|
|
||||||
# rows that have a single 1 are okay
|
|
||||||
# anything else requires substitution (unless all 0)
|
|
||||||
# pivots may be fewer than the rows
|
|
||||||
# remaining rows should be 0s
|
|
||||||
for row_i, row_pivot in enumerate(pivots):
|
|
||||||
rowsym = rref.row(row_i)
|
|
||||||
# yipee! nothign to report
|
|
||||||
if row_solved(rowsym, row_pivot):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# a grouping
|
|
||||||
group_name = "GRP_%u" % row_i
|
|
||||||
rowdsf = row_sym2dsf(rowsym, names)
|
|
||||||
|
|
||||||
state.subs[group_name] = rowdsf
|
|
||||||
# Add the new variables
|
|
||||||
state.names.add(group_name)
|
|
||||||
# Remove substituted variables
|
|
||||||
# Note: variables may appear multiple times
|
|
||||||
state.names.difference_update(OrderedSet(rowdsf.keys()))
|
|
||||||
pivot_name = names[row_pivot]
|
|
||||||
state.pivots[group_name] = pivot_name
|
|
||||||
if verbose:
|
|
||||||
print("%s (%s): %s" % (group_name, pivot_name, rowdsf))
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
def run(fnout, fn_ins, simplify=False, corner=None, rm_zero=False, verbose=0):
|
|
||||||
print('Loading data')
|
|
||||||
|
|
||||||
assert len(fn_ins) > 0
|
|
||||||
state = State.load(
|
|
||||||
fn_ins, simplify=simplify, corner=corner, rm_zero=rm_zero)
|
|
||||||
state_rref(state, verbose=verbose)
|
|
||||||
state.print_stats()
|
|
||||||
if fnout:
|
|
||||||
with open(fnout, 'w') as fout:
|
|
||||||
write_state(state, fout)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Compute reduced row echelon (RREF) to form sub.json (variable groups)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--simplify', action='store_true', help='')
|
|
||||||
parser.add_argument('--corner', default="slow_max", help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--rm-zero', action='store_true', help='Remove ZERO elements')
|
|
||||||
parser.add_argument(
|
|
||||||
'--speed-json',
|
|
||||||
default='build_speed/speed.json',
|
|
||||||
help='Provides speed index to name translation')
|
|
||||||
parser.add_argument('--out', help='Output sub.json substitution result')
|
|
||||||
parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(
|
|
||||||
fnout=args.out,
|
|
||||||
fn_ins=fns_in,
|
|
||||||
simplify=args.simplify,
|
|
||||||
corner=args.corner,
|
|
||||||
rm_zero=args.rm_zero,
|
|
||||||
verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, load_sub
|
|
||||||
import timfuz
|
|
||||||
import numpy as np
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import timfuz_solve
|
|
||||||
import scipy.optimize as optimize
|
|
||||||
|
|
||||||
|
|
||||||
def mkestimate(Anp, b):
|
|
||||||
'''
|
|
||||||
Ballpark upper bound estimate assuming variables contribute all of the delay in their respective row
|
|
||||||
Return the min of all of the occurances
|
|
||||||
|
|
||||||
XXX: should this be corner adjusted?
|
|
||||||
'''
|
|
||||||
cols = len(Anp[0])
|
|
||||||
x0 = np.array([1e3 for _x in range(cols)])
|
|
||||||
for row_np, row_b in zip(Anp, b):
|
|
||||||
for coli, val in enumerate(row_np):
|
|
||||||
# favor non-trivial values
|
|
||||||
if val <= 0:
|
|
||||||
continue
|
|
||||||
# Scale by number occurances
|
|
||||||
ub = row_b / val
|
|
||||||
if ub <= 0:
|
|
||||||
continue
|
|
||||||
if x0[coli] == 0:
|
|
||||||
x0[coli] = ub
|
|
||||||
else:
|
|
||||||
x0[coli] = min(x0[coli], ub)
|
|
||||||
# reject solutions that don't provide a seed value
|
|
||||||
# these lead to bad optimizations
|
|
||||||
assert sum(x0) != 0
|
|
||||||
return x0
|
|
||||||
|
|
||||||
|
|
||||||
def run_corner(
|
|
||||||
Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None):
|
|
||||||
# Given timing scores for above delays (-ps)
|
|
||||||
assert type(Anp[0]) is np.ndarray, type(Anp[0])
|
|
||||||
assert type(b) is np.ndarray, type(b)
|
|
||||||
|
|
||||||
#check_feasible(Anp, b)
|
|
||||||
'''
|
|
||||||
Be mindful of signs
|
|
||||||
Have something like
|
|
||||||
timing1/timing 2 are constants
|
|
||||||
delay1 + delay2 + delay4 >= timing1
|
|
||||||
delay2 + delay3 >= timing2
|
|
||||||
|
|
||||||
But need it in compliant form:
|
|
||||||
-delay1 + -delay2 + -delay4 <= -timing1
|
|
||||||
-delay2 + -delay3 <= -timing2
|
|
||||||
'''
|
|
||||||
rows = len(Anp)
|
|
||||||
cols = len(Anp[0])
|
|
||||||
|
|
||||||
print('Unique delay elements: %d' % len(names))
|
|
||||||
print('Input paths')
|
|
||||||
print(' # timing scores: %d' % len(b))
|
|
||||||
print(' Rows: %d' % rows)
|
|
||||||
'''
|
|
||||||
You must have at least as many things to optimize as variables
|
|
||||||
That is, the system must be plausibly constrained for it to attempt a solve
|
|
||||||
If not, you'll get a message like
|
|
||||||
TypeError: Improper input: N=3 must not exceed M=2
|
|
||||||
'''
|
|
||||||
if rows < cols:
|
|
||||||
raise Exception("rows must be >= cols")
|
|
||||||
|
|
||||||
tlast = [None]
|
|
||||||
iters = [0]
|
|
||||||
printn = [0]
|
|
||||||
|
|
||||||
def progress_print():
|
|
||||||
iters[0] += 1
|
|
||||||
if tlast[0] is None:
|
|
||||||
tlast[0] = time.time()
|
|
||||||
if time.time() - tlast[0] > 1.0:
|
|
||||||
sys.stdout.write('I:%d ' % iters[0])
|
|
||||||
tlast[0] = time.time()
|
|
||||||
printn[0] += 1
|
|
||||||
if printn[0] % 10 == 0:
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def func(params):
|
|
||||||
progress_print()
|
|
||||||
return (b - np.dot(Anp, params))
|
|
||||||
|
|
||||||
print('')
|
|
||||||
# Now find smallest values for delay constants
|
|
||||||
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
|
|
||||||
print('Running leastsq w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
|
|
||||||
# starting at 0 completes quicky, but gives a solution near 0 with terrible results
|
|
||||||
# maybe give a starting estimate to the smallest net delay with the indicated variable
|
|
||||||
#x0 = np.array([1000.0 for _x in range(cols)])
|
|
||||||
print('Creating x0 estimate')
|
|
||||||
x0 = mkestimate(Anp, b)
|
|
||||||
|
|
||||||
print('Solving')
|
|
||||||
res = optimize.least_squares(func, x0, bounds=(0, float('inf')))
|
|
||||||
print('Done')
|
|
||||||
|
|
||||||
if outfn:
|
|
||||||
timfuz_solve.solve_save(outfn, res.x, names, corner, verbose=verbose)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Solve timing solution using least squares objective function')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--massage',
|
|
||||||
action='store_true',
|
|
||||||
help='Derive additional constraints to improve solution')
|
|
||||||
parser.add_argument(
|
|
||||||
'--sub-json', help='Group substitutions to make fully ranked')
|
|
||||||
parser.add_argument('--corner', required=True, default="slow_max", help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--out', default=None, help='output timing delay .json')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
sub_json = None
|
|
||||||
if args.sub_json:
|
|
||||||
sub_json = load_sub(args.sub_json)
|
|
||||||
|
|
||||||
try:
|
|
||||||
timfuz_solve.run(
|
|
||||||
run_corner=run_corner,
|
|
||||||
sub_json=sub_json,
|
|
||||||
fns_in=fns_in,
|
|
||||||
corner=args.corner,
|
|
||||||
massage=args.massage,
|
|
||||||
outfn=args.out,
|
|
||||||
verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import scipy.optimize as optimize
|
|
||||||
from timfuz import Benchmark, load_sub, A_ub_np2d, acorner2csv, corner_s2i
|
|
||||||
import numpy as np
|
|
||||||
import glob
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import timfuz_solve
|
|
||||||
|
|
||||||
|
|
||||||
def run_corner(
|
|
||||||
Anp, b, names, corner, verbose=False, opts={}, meta={}, outfn=None):
|
|
||||||
if len(Anp) == 0:
|
|
||||||
print('WARNING: zero equations')
|
|
||||||
if outfn:
|
|
||||||
timfuz_solve.solve_save(outfn, [], [], corner)
|
|
||||||
return
|
|
||||||
maxcorner = {
|
|
||||||
'slow_max': True,
|
|
||||||
'slow_min': False,
|
|
||||||
'fast_max': True,
|
|
||||||
'fast_min': False,
|
|
||||||
}[corner]
|
|
||||||
|
|
||||||
# Given timing scores for above delays (-ps)
|
|
||||||
assert type(Anp[0]) is np.ndarray, type(Anp[0])
|
|
||||||
assert type(b) is np.ndarray, type(b)
|
|
||||||
|
|
||||||
#check_feasible(Anp, b)
|
|
||||||
'''
|
|
||||||
Be mindful of signs
|
|
||||||
t1, t2: total delay contants
|
|
||||||
d1, d2..: variables to solve for
|
|
||||||
|
|
||||||
Max corner intuitive form:
|
|
||||||
d1 + d2 + d4 >= t1
|
|
||||||
d2 + d3 >= t2
|
|
||||||
|
|
||||||
But need it in compliant form:
|
|
||||||
-d1 + -d2 + -d4 <= -t1
|
|
||||||
-d2 + -d3 <= -t2
|
|
||||||
Minimize delay elements
|
|
||||||
|
|
||||||
|
|
||||||
Min corner intuitive form:
|
|
||||||
d1 + d2 + d4 <= t1
|
|
||||||
d2 + d3 <= t2
|
|
||||||
Maximize delay elements
|
|
||||||
'''
|
|
||||||
|
|
||||||
rows = len(Anp)
|
|
||||||
cols = len(Anp[0])
|
|
||||||
if maxcorner:
|
|
||||||
print('maxcorner => scaling to solution form...')
|
|
||||||
b_ub = -1.0 * b
|
|
||||||
#A_ub = -1.0 * Anp
|
|
||||||
A_ub = [-1.0 * x for x in Anp]
|
|
||||||
else:
|
|
||||||
print('mincorner => no scaling required')
|
|
||||||
b_ub = b
|
|
||||||
A_ub = Anp
|
|
||||||
|
|
||||||
print('Creating misc constants...')
|
|
||||||
# Minimization function scalars
|
|
||||||
# Treat all logic elements as equally important
|
|
||||||
if maxcorner:
|
|
||||||
# Best result are min delays
|
|
||||||
c = [1 for _i in range(len(names))]
|
|
||||||
else:
|
|
||||||
# Best result are max delays
|
|
||||||
c = [-1 for _i in range(len(names))]
|
|
||||||
# Delays cannot be negative
|
|
||||||
# (this is also the default constraint)
|
|
||||||
#bounds = [(0, None) for _i in range(len(names))]
|
|
||||||
# Also you can provide one to apply to all
|
|
||||||
bounds = (0, None)
|
|
||||||
|
|
||||||
# Seems to take about rows + 3 iterations
|
|
||||||
# Give some margin
|
|
||||||
#maxiter = int(1.1 * rows + 100)
|
|
||||||
#maxiter = max(1000, int(1000 * rows + 1000))
|
|
||||||
# Most of the time I want it to just keep going unless I ^C it
|
|
||||||
maxiter = 1000000
|
|
||||||
|
|
||||||
if verbose >= 2:
|
|
||||||
print('b_ub', b)
|
|
||||||
print('Unique delay elements: %d' % len(names))
|
|
||||||
print(' # delay minimization weights: %d' % len(c))
|
|
||||||
print(' # delay constraints: %d' % len(bounds))
|
|
||||||
print('Input paths')
|
|
||||||
print(' # timing scores: %d' % len(b))
|
|
||||||
print(' Rows: %d' % rows)
|
|
||||||
|
|
||||||
tlast = [time.time()]
|
|
||||||
iters = [0]
|
|
||||||
printn = [0]
|
|
||||||
|
|
||||||
def callback(xk, **kwargs):
|
|
||||||
iters[0] = xk['nit']
|
|
||||||
if time.time() - tlast[0] > 1.0:
|
|
||||||
sys.stdout.write('I:%d ' % xk['nit'])
|
|
||||||
tlast[0] = time.time()
|
|
||||||
printn[0] += 1
|
|
||||||
if printn[0] % 10 == 0:
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
print('')
|
|
||||||
# Now find smallest values for delay constants
|
|
||||||
# Due to input bounds (ex: column limit), some delay elements may get eliminated entirely
|
|
||||||
# https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.optimize.linprog.html
|
|
||||||
print('Running linprog w/ %d r, %d c (%d name)' % (rows, cols, len(names)))
|
|
||||||
res = optimize.linprog(
|
|
||||||
c,
|
|
||||||
A_ub=A_ub,
|
|
||||||
b_ub=b_ub,
|
|
||||||
bounds=bounds,
|
|
||||||
callback=callback,
|
|
||||||
options={
|
|
||||||
"disp": True,
|
|
||||||
'maxiter': maxiter,
|
|
||||||
'bland': True,
|
|
||||||
'tol': 1e-6,
|
|
||||||
})
|
|
||||||
nonzeros = 0
|
|
||||||
print('Ran %d iters' % iters[0])
|
|
||||||
if res.success:
|
|
||||||
print('Result sample (%d elements)' % (len(res.x)))
|
|
||||||
plim = 3
|
|
||||||
for xi, (name, x) in enumerate(zip(names, res.x)):
|
|
||||||
nonzero = x >= 0.001
|
|
||||||
if nonzero:
|
|
||||||
nonzeros += 1
|
|
||||||
#if nonzero and (verbose >= 1 or xi > 30):
|
|
||||||
if nonzero and (verbose or (
|
|
||||||
(nonzeros < 100 or nonzeros % 20 == 0) and nonzeros <= plim)):
|
|
||||||
print(' % 4u % -80s % 10.1f' % (xi, name, x))
|
|
||||||
print('Delay on %d / %d' % (nonzeros, len(res.x)))
|
|
||||||
|
|
||||||
if outfn:
|
|
||||||
timfuz_solve.solve_save(
|
|
||||||
outfn, res.x, names, corner, verbose=verbose)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Solve timing solution using linear programming inequalities')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--massage', action='store_true', help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--bounds-csv', help='Previous solve result starting point')
|
|
||||||
parser.add_argument(
|
|
||||||
'--sub-json', help='Group substitutions to make fully ranked')
|
|
||||||
parser.add_argument('--corner', required=True, default="slow_max", help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--out', default=None, help='output timing delay .json')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
sub_json = None
|
|
||||||
if args.sub_json:
|
|
||||||
sub_json = load_sub(args.sub_json)
|
|
||||||
|
|
||||||
try:
|
|
||||||
timfuz_solve.run(
|
|
||||||
run_corner=run_corner,
|
|
||||||
sub_json=sub_json,
|
|
||||||
bounds_csv=args.bounds_csv,
|
|
||||||
fns_in=fns_in,
|
|
||||||
corner=args.corner,
|
|
||||||
massage=args.massage,
|
|
||||||
outfn=args.out,
|
|
||||||
verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, load_sub, load_bounds, loadc_Ads_b
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
def run(fns_in, corner, bounds_csv, verbose=False):
|
|
||||||
print('Loading data')
|
|
||||||
Ads, borig = loadc_Ads_b(fns_in, corner)
|
|
||||||
|
|
||||||
bounds = load_bounds(bounds_csv, corner)
|
|
||||||
# verify is flattened
|
|
||||||
for k in bounds.keys():
|
|
||||||
assert 'GROUP_' not in k, 'Must operate on flattened bounds'
|
|
||||||
|
|
||||||
# compute our timing model delay at the given corner
|
|
||||||
bgots = []
|
|
||||||
for row_ds in Ads:
|
|
||||||
delays = [n * bounds[x] for x, n in row_ds.items()]
|
|
||||||
bgots.append(sum(delays))
|
|
||||||
|
|
||||||
ses = (np.asarray(bgots) - np.asarray(borig))**2
|
|
||||||
mse = (ses).mean(axis=None)
|
|
||||||
print('MSE aggregate: %0.1f' % mse)
|
|
||||||
print('Min SE: %0.1f' % min(ses))
|
|
||||||
print('Max SE: %0.1f' % max(ses))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Report a timing fit score')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--corner', required=True, default="slow_max", help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'--bounds-csv',
|
|
||||||
required=True,
|
|
||||||
help='Previous solve result starting point')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
# Store options in dict to ease passing through functions
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4i.csv')
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(
|
|
||||||
fns_in=fns_in,
|
|
||||||
corner=args.corner,
|
|
||||||
bounds_csv=args.bounds_csv,
|
|
||||||
verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
build
|
|
||||||
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
all: build/speed.json
|
|
||||||
|
|
||||||
build/node.txt: speed_json.py generate.tcl
|
|
||||||
mkdir -p build
|
|
||||||
cd build && ${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
|
|
||||||
build/speed.json: build/node.txt
|
|
||||||
cd build && python3 ../speed_json.py speed_model.txt node.txt speed.json
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf build
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
Generates speed.json, describing speed index to string translation.
|
|
||||||
These indices appear to be fixed between runtimes.
|
|
||||||
It is unknown how stable they are across versions.
|
|
||||||
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
proc pin_info {pin} {
|
|
||||||
set cell [get_cells -of_objects $pin]
|
|
||||||
set bel [get_bels -of_objects $cell]
|
|
||||||
set site [get_sites -of_objects $bel]
|
|
||||||
return "$site $bel"
|
|
||||||
}
|
|
||||||
|
|
||||||
proc pin_bel {pin} {
|
|
||||||
set cell [get_cells -of_objects $pin]
|
|
||||||
set bel [get_bels -of_objects $cell]
|
|
||||||
return $bel
|
|
||||||
}
|
|
||||||
|
|
||||||
proc build_design_full {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
read_verilog ../top.v
|
|
||||||
read_verilog ../../src/picorv32.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
#set_property LOCK_PINS {I0:A1 I1:A2 I2:A3 I3:A4 I4:A5 I5:A6} \
|
|
||||||
# [get_cells -quiet -filter {REF_NAME == LUT6} -hierarchical]
|
|
||||||
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
proc build_design_synth {} {
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
|
|
||||||
read_verilog ../top.v
|
|
||||||
read_verilog ../picorv32.v
|
|
||||||
synth_design -top top
|
|
||||||
}
|
|
||||||
|
|
||||||
# WARNING: [Common 17-673] Cannot get value of property 'FORWARD' because this property is not valid in conjunction with other property setting on this object.
|
|
||||||
# WARNING: [Common 17-673] Cannot get value of property 'REVERSE' because this property is not valid in conjunction with other property setting on this object.
|
|
||||||
proc speed_models1 {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/speed_model.txt" w]
|
|
||||||
# list_property [lindex [get_speed_models] 0]
|
|
||||||
set speed_models [get_speed_models]
|
|
||||||
set properties [list_property [lindex $speed_models 0]]
|
|
||||||
# "CLASS DELAY FAST_MAX FAST_MIN IS_INSTANCE_SPECIFIC NAME NAME_LOGICAL SLOW_MAX SLOW_MIN SPEED_INDEX TYPE"
|
|
||||||
puts $fp $properties
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
foreach speed_model $speed_models {
|
|
||||||
foreach property $properties {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp [get_property $property $speed_model]
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
proc speed_models2 {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/speed_model.txt" w]
|
|
||||||
# list_property [lindex [get_speed_models] 0]
|
|
||||||
set speed_models [get_speed_models]
|
|
||||||
puts "Items: [llength $speed_models]"
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
# Not all objects have the same properties
|
|
||||||
# But they do seem to have the same list
|
|
||||||
set properties [list_property [lindex $speed_models 0]]
|
|
||||||
foreach speed_model $speed_models {
|
|
||||||
set needspace 0
|
|
||||||
foreach property $properties {
|
|
||||||
set val [get_property $property $speed_model]
|
|
||||||
if {"$val" ne ""} {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp "$property:$val"
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# For cost codes
|
|
||||||
# Items: 2663055s
|
|
||||||
# Hmm too much
|
|
||||||
# Lets filter out items we really want
|
|
||||||
proc nodes_all {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/node_all.txt" w]
|
|
||||||
set items [get_nodes]
|
|
||||||
puts "Items: [llength $items]"
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
set properties [list_property [lindex $items 0]]
|
|
||||||
foreach item $items {
|
|
||||||
set needspace 0
|
|
||||||
foreach property $properties {
|
|
||||||
set val [get_property $property $item]
|
|
||||||
if {"$val" ne ""} {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp "$property:$val"
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only writes out items with unique cost codes
|
|
||||||
# (much faster)
|
|
||||||
proc nodes_unique_cc {} {
|
|
||||||
set outdir "."
|
|
||||||
set fp [open "$outdir/node.txt" w]
|
|
||||||
set items [get_nodes]
|
|
||||||
puts "Computing cost codes with [llength $items] items"
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
set properties [list_property [lindex $items 0]]
|
|
||||||
set cost_codes_known [dict create]
|
|
||||||
set itemi 0
|
|
||||||
foreach item $items {
|
|
||||||
incr itemi
|
|
||||||
set cost_code [get_property COST_CODE $item]
|
|
||||||
if {[ dict exists $cost_codes_known $cost_code ]} {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
puts " Adding cost code $cost_code @ item $itemi"
|
|
||||||
dict set cost_codes_known $cost_code 1
|
|
||||||
|
|
||||||
set needspace 0
|
|
||||||
foreach property $properties {
|
|
||||||
set val [get_property $property $item]
|
|
||||||
if {"$val" ne ""} {
|
|
||||||
if $needspace {
|
|
||||||
puts -nonewline $fp " "
|
|
||||||
}
|
|
||||||
puts -nonewline $fp "$property:$val"
|
|
||||||
set needspace 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
puts $fp ""
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
build_design_full
|
|
||||||
speed_models2
|
|
||||||
nodes_unique_cc
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def load_speed(fin):
|
|
||||||
speed_models = {}
|
|
||||||
speed_types = {}
|
|
||||||
for l in fin:
|
|
||||||
delay = {}
|
|
||||||
l = l.strip()
|
|
||||||
for kvs in l.split():
|
|
||||||
name, value = kvs.split(':')
|
|
||||||
name = name.lower()
|
|
||||||
if name in ('class', ):
|
|
||||||
continue
|
|
||||||
if name in ('speed_index', ):
|
|
||||||
value = int(value)
|
|
||||||
if name == 'type':
|
|
||||||
speed_types.setdefault(value, {})
|
|
||||||
delay[name] = value
|
|
||||||
delayk = delay['name']
|
|
||||||
if delayk in delay:
|
|
||||||
raise Exception("Duplicate name")
|
|
||||||
|
|
||||||
if "name" in delay and "name_logical" in delay:
|
|
||||||
# Always true
|
|
||||||
if delay['name'] != delay['name_logical']:
|
|
||||||
raise Exception("nope!")
|
|
||||||
# Found a counter example
|
|
||||||
if 0 and delay['name'] != delay['forward']:
|
|
||||||
# ('BSW_NONTLFW_TLRV', '_BSW_LONG_NONTLFORWARD')
|
|
||||||
print(delay['name'], delay['forward'])
|
|
||||||
raise Exception("nope!")
|
|
||||||
# Found a counter example
|
|
||||||
if 0 and delay['forward'] != delay['reverse']:
|
|
||||||
# _BSW_LONG_NONTLFORWARD _BSW_LONG_TLREVERSE
|
|
||||||
print(delay['forward'], delay['reverse'])
|
|
||||||
raise Exception("nope!")
|
|
||||||
|
|
||||||
speed_models[delayk] = delay
|
|
||||||
return speed_models, speed_types
|
|
||||||
|
|
||||||
|
|
||||||
def load_cost_code(fin):
|
|
||||||
# COST_CODE:4 COST_CODE_NAME:SLOWSINGLE
|
|
||||||
cost_codes = {}
|
|
||||||
for l in fin:
|
|
||||||
lj = {}
|
|
||||||
l = l.strip()
|
|
||||||
for kvs in l.split():
|
|
||||||
name, value = kvs.split(':')
|
|
||||||
name = name.lower()
|
|
||||||
lj[name] = value
|
|
||||||
cost_code = {
|
|
||||||
'name': lj['cost_code_name'],
|
|
||||||
'code': int(lj['cost_code']),
|
|
||||||
# Hmm is this unique per type?
|
|
||||||
#'speed_class': int(lj['speed_class']),
|
|
||||||
}
|
|
||||||
|
|
||||||
cost_codes[cost_code['name']] = cost_code
|
|
||||||
return cost_codes
|
|
||||||
|
|
||||||
|
|
||||||
def run(speed_fin, node_fin, fout, verbose=0):
|
|
||||||
print('Loading data')
|
|
||||||
speed_models, speed_types = load_speed(speed_fin)
|
|
||||||
cost_codes = load_cost_code(node_fin)
|
|
||||||
|
|
||||||
j = {
|
|
||||||
'speed_model': speed_models,
|
|
||||||
'speed_type': speed_types,
|
|
||||||
'cost_code': cost_codes,
|
|
||||||
}
|
|
||||||
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Timing fuzzer')
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
parser.add_argument(
|
|
||||||
'speed_fn_in', default='/dev/stdin', nargs='?', help='Input file')
|
|
||||||
parser.add_argument(
|
|
||||||
'node_fn_in', default='/dev/stdin', nargs='?', help='Input file')
|
|
||||||
parser.add_argument(
|
|
||||||
'fn_out', default='/dev/stdout', nargs='?', help='Output file')
|
|
||||||
args = parser.parse_args()
|
|
||||||
run(
|
|
||||||
open(args.speed_fn_in, 'r'),
|
|
||||||
open(args.node_fn_in, 'r'),
|
|
||||||
open(args.fn_out, 'w'),
|
|
||||||
verbose=args.verbose)
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
module top(input clk, stb, di, output do);
|
|
||||||
reg dor;
|
|
||||||
always @(posedge clk) begin
|
|
||||||
dor <= stb & di;
|
|
||||||
end
|
|
||||||
assign do = dor;
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,89 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def write_state(state, fout):
|
|
||||||
j = {
|
|
||||||
'names': dict([(x, None) for x in state.names]),
|
|
||||||
'drop_names': list(state.drop_names),
|
|
||||||
'base_names': list(state.base_names),
|
|
||||||
'subs': dict([(name, values) for name, values in state.subs.items()]),
|
|
||||||
'pivots': state.pivots,
|
|
||||||
}
|
|
||||||
json.dump(j, fout, sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
def gen_rows(fn_ins):
|
|
||||||
for fn_in in fn_ins:
|
|
||||||
try:
|
|
||||||
print('Loading %s' % fn_in)
|
|
||||||
j = json.load(open(fn_in, 'r'))
|
|
||||||
|
|
||||||
group0 = list(j['subs'].values())[0]
|
|
||||||
value0 = list(group0.values())[0]
|
|
||||||
if type(value0) is float:
|
|
||||||
print("WARNING: skipping old format JSON")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
print("Value OK")
|
|
||||||
|
|
||||||
for sub in j['subs'].values():
|
|
||||||
row_ds = {}
|
|
||||||
# TODO: convert to gcd
|
|
||||||
# den may not always be 0
|
|
||||||
# lazy solution...just multiply out all the fractions
|
|
||||||
n = 1
|
|
||||||
for _var, (_num, den) in sub.items():
|
|
||||||
n *= den
|
|
||||||
|
|
||||||
for var, (num, den) in sub.items():
|
|
||||||
num2 = n * num
|
|
||||||
assert num2 % den == 0
|
|
||||||
row_ds[var] = num2 / den
|
|
||||||
yield row_ds
|
|
||||||
except:
|
|
||||||
print("Error processing %s" % fn_in)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def run(fnout, fn_ins, verbose=0):
|
|
||||||
print('Loading data')
|
|
||||||
|
|
||||||
with open(fnout, 'w') as fout:
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for row_ds in gen_rows(fn_ins):
|
|
||||||
ico = '1'
|
|
||||||
out_b = [1e9, 1e9, 1e9, 1e9]
|
|
||||||
items = [ico, ' '.join(['%u' % x for x in out_b])]
|
|
||||||
|
|
||||||
for k, v in sorted(row_ds.items()):
|
|
||||||
items.append('%i %s' % (v, k))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Convert substitution groups into .csv to allow incremental rref results'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', action='store_true', help='')
|
|
||||||
parser.add_argument('--out', help='Output csv')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='sub.json input files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(fnout=args.out, fn_ins=args.fns_in, verbose=args.verbose)
|
|
||||||
finally:
|
|
||||||
print('Exiting after %s' % bench)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import timfuz
|
|
||||||
from timfuz import loadc_Ads_bs, Ads2bounds, PREFIX_W, PREFIX_P, PREFIX_SW_EI, PREFIX_SW_EO, PREFIX_SW_I, sw_ei_s2vals, sw_eo_s2vals, sw_i_s2vals
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
def add_pip_wire(tilej, bounds, verbose=False):
|
|
||||||
'''
|
|
||||||
We know all possible pips and wires from tilej
|
|
||||||
Iterate over them and see if a result was generated
|
|
||||||
'''
|
|
||||||
used_bounds = set()
|
|
||||||
|
|
||||||
for tile in tilej['tiles'].values():
|
|
||||||
|
|
||||||
def addk(pws, prefix, k, v):
|
|
||||||
variable = prefix + ':' + v
|
|
||||||
val = bounds.get(variable, None)
|
|
||||||
# print(variable, val)
|
|
||||||
if val:
|
|
||||||
used_bounds.add(variable)
|
|
||||||
else:
|
|
||||||
val = [None, None, None, None]
|
|
||||||
pws[k] = val
|
|
||||||
|
|
||||||
pips = tile['pips']
|
|
||||||
for k, v in pips.items():
|
|
||||||
#pips[k] = bounds.get('PIP_' + v, [None, None, None, None])
|
|
||||||
addk(pips, PREFIX_P, k, v)
|
|
||||||
|
|
||||||
wires = tile['wires']
|
|
||||||
for k, v in wires.items():
|
|
||||||
#wires[k] = bounds.get('WIRE_' + v, [None, None, None, None])
|
|
||||||
addk(wires, PREFIX_W, k, v)
|
|
||||||
|
|
||||||
# verify all the variables that should be used were applied
|
|
||||||
# ...except tilecon may be an ROI and we solved everything
|
|
||||||
print(
|
|
||||||
"Interconnect: %u / %u variables used" %
|
|
||||||
(len(used_bounds), len(bounds)))
|
|
||||||
if verbose:
|
|
||||||
print('Remainder: %s' % (set(bounds.keys()) - used_bounds))
|
|
||||||
|
|
||||||
|
|
||||||
def add_sites(tilej, bounds):
|
|
||||||
# XXX: no source of truth currently
|
|
||||||
# is there a way we could get this?
|
|
||||||
# Naming discussion https://github.com/SymbiFlow/prjxray/pull/126
|
|
||||||
|
|
||||||
sitej = tilej.setdefault('sites', {})
|
|
||||||
|
|
||||||
def add_ei():
|
|
||||||
for variable, bound in bounds[PREFIX_SW_EI].items():
|
|
||||||
site_type, src_site_pin, dst_bel_type, dst_bel_pin = sw_ei_s2vals(
|
|
||||||
variable)
|
|
||||||
# ex: SLICEL:AX->AFF.D
|
|
||||||
k = (
|
|
||||||
'%s:%s->%s.%s' %
|
|
||||||
(site_type, src_site_pin, dst_bel_type, dst_bel_pin))
|
|
||||||
sitej.setdefault(site_type, {})[k] = bound
|
|
||||||
|
|
||||||
def add_eo():
|
|
||||||
for variable, bound in bounds[PREFIX_SW_EO].items():
|
|
||||||
site_type, src_bel_type, src_bel_pin, dst_site_pin = sw_eo_s2vals(
|
|
||||||
variable)
|
|
||||||
# ex: SLICEL:AFF.Q->AQ
|
|
||||||
k = (
|
|
||||||
'%s:%s.%s->%s' %
|
|
||||||
(site_type, src_bel_type, src_bel_pin, dst_site_pin))
|
|
||||||
sitej.setdefault(site_type, {})[k] = bound
|
|
||||||
|
|
||||||
def add_i():
|
|
||||||
for variable, bound in bounds[PREFIX_SW_I].items():
|
|
||||||
site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin = sw_i_s2vals(
|
|
||||||
variable)
|
|
||||||
# ex: SLICEL:LUT6.O2->AFF.D
|
|
||||||
k = (
|
|
||||||
'%s:%s.%s->%s.%s' %
|
|
||||||
(site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin))
|
|
||||||
sitej.setdefault(site_type, {})[k] = bound
|
|
||||||
|
|
||||||
add_ei()
|
|
||||||
add_eo()
|
|
||||||
add_i()
|
|
||||||
print('Sites: added %u sites' % len(sitej))
|
|
||||||
print('Added site wires')
|
|
||||||
print(' Site external in: %u' % len(bounds[PREFIX_SW_EI]))
|
|
||||||
print(' Site external out: %u' % len(bounds[PREFIX_SW_EO]))
|
|
||||||
print(' Site internal: %u' % len(bounds[PREFIX_SW_I]))
|
|
||||||
|
|
||||||
|
|
||||||
def sep_bounds(bounds):
|
|
||||||
pw = {}
|
|
||||||
sites = {PREFIX_SW_EI: {}, PREFIX_SW_EO: {}, PREFIX_SW_I: {}}
|
|
||||||
for k, v in bounds.items():
|
|
||||||
prefix = k.split(':')[0]
|
|
||||||
if prefix in (PREFIX_W, PREFIX_P):
|
|
||||||
pw[k] = v
|
|
||||||
elif prefix in (PREFIX_SW_EI, PREFIX_SW_EO, PREFIX_SW_I):
|
|
||||||
sites[prefix][k] = v
|
|
||||||
else:
|
|
||||||
assert 0, 'Unknown delay: %s %s' % (k, prefix)
|
|
||||||
return pw, sites
|
|
||||||
|
|
||||||
|
|
||||||
def run(fns_in, fnout, tile_json_fn, verbose=False):
|
|
||||||
# modified in place
|
|
||||||
tilej = json.load(open(tile_json_fn, 'r'))
|
|
||||||
|
|
||||||
for fnin in fns_in:
|
|
||||||
Ads, bs = loadc_Ads_bs([fnin])
|
|
||||||
bounds = Ads2bounds(Ads, bs)
|
|
||||||
bounds_pw, bounds_sites = sep_bounds(bounds)
|
|
||||||
print(len(bounds), len(bounds_pw), len(bounds_sites))
|
|
||||||
|
|
||||||
add_pip_wire(tilej, bounds_pw)
|
|
||||||
add_sites(tilej, bounds_sites)
|
|
||||||
|
|
||||||
timfuz.tilej_stats(tilej)
|
|
||||||
|
|
||||||
json.dump(
|
|
||||||
tilej,
|
|
||||||
open(fnout, 'w'),
|
|
||||||
sort_keys=True,
|
|
||||||
indent=4,
|
|
||||||
separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Substitute timgrid timing model names for real timing values')
|
|
||||||
parser.add_argument(
|
|
||||||
'--timgrid-s',
|
|
||||||
default='../../timgrid/build/timgrid-s.json',
|
|
||||||
help='tilegrid timing delay symbolic input (timgrid-s.json)')
|
|
||||||
parser.add_argument(
|
|
||||||
'--out',
|
|
||||||
default='build/timgrid-vc.json',
|
|
||||||
help='tilegrid timing delay values at corner (timgrid-vc.json)')
|
|
||||||
parser.add_argument(
|
|
||||||
'fn_ins', nargs='+', help='Input flattened timing csv (flat.csv)')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(args.fn_ins, args.out, args.timgrid_s, verbose=False)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,844 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import math
|
|
||||||
import numpy as np
|
|
||||||
from collections import OrderedDict
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import copy
|
|
||||||
import sys
|
|
||||||
import random
|
|
||||||
import glob
|
|
||||||
from fractions import Fraction
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from benchmark import Benchmark
|
|
||||||
|
|
||||||
# prefix to make easier to track
|
|
||||||
# models do not overlap between PIPs and WIREs
|
|
||||||
PREFIX_W = 'WIRE'
|
|
||||||
PREFIX_P = 'PIP'
|
|
||||||
# extneral site wire (ie site a to b)
|
|
||||||
PREFIX_SW_EI = 'SITEW-EI'
|
|
||||||
PREFIX_SW_EO = 'SITEW-EO'
|
|
||||||
# internal site wire (ie bel a to b within a site)
|
|
||||||
PREFIX_SW_I = 'SITEW-I'
|
|
||||||
|
|
||||||
|
|
||||||
def sw_ei_vals2s(site_type, src_site_pin, dst_bel, dst_bel_pin):
|
|
||||||
'''Pack site wire components into a variable string'''
|
|
||||||
return '%s:%s:%s:%s:%s' % (
|
|
||||||
PREFIX_SW_EI, site_type, src_site_pin, dst_bel, dst_bel_pin)
|
|
||||||
|
|
||||||
|
|
||||||
def sw_ei_s2vals(s):
|
|
||||||
prefix, site_type, src_site_pin, dst_bel, dst_bel_pin = s.split(':')
|
|
||||||
assert prefix == PREFIX_SW_EI
|
|
||||||
return site_type, src_site_pin, dst_bel, dst_bel_pin
|
|
||||||
|
|
||||||
|
|
||||||
def sw_eo_vals2s(site_type, src_bel, src_bel_pin, dst_site_pin):
|
|
||||||
return '%s:%s:%s:%s:%s' % (
|
|
||||||
PREFIX_SW_EO, site_type, src_bel, src_bel_pin, dst_site_pin)
|
|
||||||
|
|
||||||
|
|
||||||
def sw_eo_s2vals(s):
|
|
||||||
prefix, site_type, src_bel, src_bel_pin, dst_site_pin = s.split(':')
|
|
||||||
assert prefix == PREFIX_SW_EO
|
|
||||||
return site_type, src_bel, src_bel_pin, dst_site_pin
|
|
||||||
|
|
||||||
|
|
||||||
def sw_i_vals2s(site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin):
|
|
||||||
return '%s:%s:%s:%s:%s:%s' % (
|
|
||||||
PREFIX_SW_I, site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin)
|
|
||||||
|
|
||||||
|
|
||||||
def sw_i_s2vals(s):
|
|
||||||
prefix, site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin = s.split(
|
|
||||||
':')
|
|
||||||
assert prefix == PREFIX_SW_I
|
|
||||||
return site_type, src_bel, src_bel_pin, dst_bel, dst_bel_pin
|
|
||||||
|
|
||||||
|
|
||||||
# Equations are filtered out until nothing is left
|
|
||||||
class SimplifiedToZero(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# http://code.activestate.com/recipes/576694/
|
|
||||||
class OrderedSet(collections.MutableSet):
|
|
||||||
def __init__(self, iterable=None):
|
|
||||||
self.end = end = []
|
|
||||||
end += [None, end, end] # sentinel node for doubly linked list
|
|
||||||
self.map = {} # key --> [key, prev, next]
|
|
||||||
if iterable is not None:
|
|
||||||
self |= iterable
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.map)
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self.map
|
|
||||||
|
|
||||||
def add(self, key):
|
|
||||||
if key not in self.map:
|
|
||||||
end = self.end
|
|
||||||
curr = end[1]
|
|
||||||
curr[2] = end[1] = self.map[key] = [key, curr, end]
|
|
||||||
|
|
||||||
def discard(self, key):
|
|
||||||
if key in self.map:
|
|
||||||
key, prev, next = self.map.pop(key)
|
|
||||||
prev[2] = next
|
|
||||||
next[1] = prev
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
end = self.end
|
|
||||||
curr = end[2]
|
|
||||||
while curr is not end:
|
|
||||||
yield curr[0]
|
|
||||||
curr = curr[2]
|
|
||||||
|
|
||||||
def __reversed__(self):
|
|
||||||
end = self.end
|
|
||||||
curr = end[1]
|
|
||||||
while curr is not end:
|
|
||||||
yield curr[0]
|
|
||||||
curr = curr[1]
|
|
||||||
|
|
||||||
def pop(self, last=True):
|
|
||||||
if not self:
|
|
||||||
raise KeyError('set is empty')
|
|
||||||
key = self.end[1][0] if last else self.end[2][0]
|
|
||||||
self.discard(key)
|
|
||||||
return key
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if not self:
|
|
||||||
return '%s()' % (self.__class__.__name__, )
|
|
||||||
return '%s(%r)' % (self.__class__.__name__, list(self))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, OrderedSet):
|
|
||||||
return len(self) == len(other) and list(self) == list(other)
|
|
||||||
return set(self) == set(other)
|
|
||||||
|
|
||||||
|
|
||||||
NAME_ZERO = OrderedSet(
|
|
||||||
[
|
|
||||||
"BSW_CLK_ZERO",
|
|
||||||
"BSW_ZERO",
|
|
||||||
"B_ZERO",
|
|
||||||
"C_CLK_ZERO",
|
|
||||||
"C_DSP_ZERO",
|
|
||||||
"C_ZERO",
|
|
||||||
"I_ZERO",
|
|
||||||
"O_ZERO",
|
|
||||||
"RC_ZERO",
|
|
||||||
"R_ZERO",
|
|
||||||
])
|
|
||||||
|
|
||||||
# csv index
|
|
||||||
corner_s2i = OrderedDict(
|
|
||||||
[
|
|
||||||
('fast_max', 0),
|
|
||||||
('fast_min', 1),
|
|
||||||
('slow_max', 2),
|
|
||||||
('slow_min', 3),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def allow_zero_eqns():
|
|
||||||
'''If true, allow a system of equations with no equations'''
|
|
||||||
return os.getenv('ALLOW_ZERO_EQN', 'N') == 'Y'
|
|
||||||
|
|
||||||
|
|
||||||
def print_eqns(A_ubd, b_ub, verbose=0, lim=3, label=''):
|
|
||||||
rows = len(b_ub)
|
|
||||||
|
|
||||||
print('Sample equations (%s) from %d r' % (label, rows))
|
|
||||||
prints = 0
|
|
||||||
#verbose = 1
|
|
||||||
for rowi, row in enumerate(A_ubd):
|
|
||||||
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
|
|
||||||
(not lim or prints < lim)):
|
|
||||||
line = ' EQN: p%u: ' % rowi
|
|
||||||
for k, v in sorted(row.items()):
|
|
||||||
line += '%u*t%d ' % (v, k)
|
|
||||||
line += '= %d' % b_ub[rowi]
|
|
||||||
print(line)
|
|
||||||
prints += 1
|
|
||||||
|
|
||||||
|
|
||||||
def print_name_eqns(A_ubd, b_ub, names, verbose=0, lim=3, label=''):
|
|
||||||
rows = len(b_ub)
|
|
||||||
|
|
||||||
print('Sample equations (%s) from %d r' % (label, rows))
|
|
||||||
prints = 0
|
|
||||||
#verbose = 1
|
|
||||||
for rowi, row in enumerate(A_ubd):
|
|
||||||
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
|
|
||||||
(not lim or prints < lim)):
|
|
||||||
line = ' EQN: p%u: ' % rowi
|
|
||||||
for k, v in sorted(row.items()):
|
|
||||||
line += '%u*%s ' % (v, names[k])
|
|
||||||
line += '= %d' % b_ub[rowi]
|
|
||||||
print(line)
|
|
||||||
prints += 1
|
|
||||||
|
|
||||||
|
|
||||||
def print_names(names, verbose=1):
|
|
||||||
print('Names: %d' % len(names))
|
|
||||||
for xi, name in enumerate(names):
|
|
||||||
print(' % 4u % -80s' % (xi, name))
|
|
||||||
|
|
||||||
|
|
||||||
def invb(b_ub):
|
|
||||||
#return [-b for b in b_ub]
|
|
||||||
return -np.array(b_ub)
|
|
||||||
|
|
||||||
|
|
||||||
def check_feasible_d(A_ubd, b_ub, names):
|
|
||||||
A_ub, b_ub_inv = Ab_d2np(A_ubd, b_ub, names)
|
|
||||||
check_feasible(A_ub, b_ub_inv)
|
|
||||||
|
|
||||||
|
|
||||||
def check_feasible(A_ub, b_ub):
|
|
||||||
sys.stdout.write('Check feasible ')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
rows = len(b_ub)
|
|
||||||
cols = len(A_ub[0])
|
|
||||||
|
|
||||||
progress = max(1, rows / 100)
|
|
||||||
|
|
||||||
# Chose a high arbitrary value for x
|
|
||||||
# Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
|
|
||||||
xs = [10e3 for _i in range(cols)]
|
|
||||||
|
|
||||||
# FIXME: use the correct np function to do this for me
|
|
||||||
# Verify bounds
|
|
||||||
#b_res = np.matmul(A_ub, xs)
|
|
||||||
#print(type(A_ub), type(xs)
|
|
||||||
#A_ub = np.array(A_ub)
|
|
||||||
#xs = np.array(xs)
|
|
||||||
#b_res = np.matmul(A_ub, xs)
|
|
||||||
def my_mul(A_ub, xs):
|
|
||||||
#print('cols', cols
|
|
||||||
#print('rows', rows
|
|
||||||
ret = [None] * rows
|
|
||||||
for row in range(rows):
|
|
||||||
this = 0
|
|
||||||
for col in range(cols):
|
|
||||||
this += A_ub[row][col] * xs[col]
|
|
||||||
ret[row] = this
|
|
||||||
return ret
|
|
||||||
|
|
||||||
b_res = my_mul(A_ub, xs)
|
|
||||||
|
|
||||||
# Verify bound was respected
|
|
||||||
for rowi, (this_b, this_b_ub) in enumerate(zip(b_res, b_ub)):
|
|
||||||
if rowi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if this_b >= this_b_ub or this_b > 0:
|
|
||||||
print(
|
|
||||||
'% 4d Want res % 10.1f <= % 10.1f <= 0' %
|
|
||||||
(rowi, this_b, this_b_ub))
|
|
||||||
raise Exception("Bad ")
|
|
||||||
print(' done')
|
|
||||||
|
|
||||||
|
|
||||||
def Ab_ub_dt2d(eqns):
|
|
||||||
'''Convert dict using the rows as keys into a list of dicts + b_ub list (ie return A_ub, b_ub)'''
|
|
||||||
#return [dict(rowt) for rowt in eqns]
|
|
||||||
rows = [(OrderedDict(rowt), b) for rowt, b in eqns.items()]
|
|
||||||
A_ubd, b_ub = zip(*rows)
|
|
||||||
return list(A_ubd), list(b_ub)
|
|
||||||
|
|
||||||
|
|
||||||
# This significantly reduces runtime
|
|
||||||
def simplify_rows(Ads, b_ub, remove_zd=False, corner=None):
|
|
||||||
'''Remove duplicate equations, taking highest delay'''
|
|
||||||
# dict of constants to highest delay
|
|
||||||
eqns = OrderedDict()
|
|
||||||
assert len(Ads) == len(b_ub), (len(Ads), len(b_ub))
|
|
||||||
|
|
||||||
assert corner is not None
|
|
||||||
minmax = {
|
|
||||||
'fast_max': max,
|
|
||||||
'fast_min': min,
|
|
||||||
'slow_max': max,
|
|
||||||
'slow_min': min,
|
|
||||||
}[corner]
|
|
||||||
# An outlier to make unknown values be ignored
|
|
||||||
T_UNK = {
|
|
||||||
'fast_max': 0,
|
|
||||||
'fast_min': 10e9,
|
|
||||||
'slow_max': 0,
|
|
||||||
'slow_min': 10e9,
|
|
||||||
}[corner]
|
|
||||||
|
|
||||||
sys.stdout.write('SimpR ')
|
|
||||||
sys.stdout.flush()
|
|
||||||
progress = int(max(1, len(b_ub) / 100))
|
|
||||||
# Equations with a total delay of zero
|
|
||||||
zero_ds = 0
|
|
||||||
# Equations with zero elements
|
|
||||||
# These should have zero delay
|
|
||||||
zero_es = 0
|
|
||||||
for loopi, (b, rowd) in enumerate(zip(b_ub, Ads)):
|
|
||||||
if loopi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
if remove_zd and not b:
|
|
||||||
zero_ds += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# think ran into these before when taking out ZERO elements
|
|
||||||
if len(rowd) == 0:
|
|
||||||
if b != 0:
|
|
||||||
assert zero_es == 0, 'Unexpected zero element row with non-zero delay'
|
|
||||||
zero_es += 1
|
|
||||||
continue
|
|
||||||
'''
|
|
||||||
Reduce any constants to canonical form
|
|
||||||
this simplifies later steps that optimize based on assuming there aren't duplicate constants
|
|
||||||
|
|
||||||
Ex:
|
|
||||||
a = 10
|
|
||||||
2 a = 30
|
|
||||||
max corner will get simplified to
|
|
||||||
a = 15
|
|
||||||
|
|
||||||
Otherwise these are not identical equations and would not get simplied
|
|
||||||
Don't try handling the general case of non-trivial equations
|
|
||||||
'''
|
|
||||||
if len(rowd) == 1:
|
|
||||||
k, v = list(rowd.items())[0]
|
|
||||||
if v != 1:
|
|
||||||
rowd = {k: 1}
|
|
||||||
b = 1.0 * b / v
|
|
||||||
|
|
||||||
rowt = Ar_ds2t(rowd)
|
|
||||||
eqns[rowt] = minmax(eqns.get(rowt, T_UNK), b)
|
|
||||||
|
|
||||||
print(' done')
|
|
||||||
|
|
||||||
print(
|
|
||||||
'Simplify rows: %d => %d rows w/ rm zd %d, rm ze %d' %
|
|
||||||
(len(b_ub), len(eqns), zero_ds, zero_es))
|
|
||||||
if len(eqns) == 0:
|
|
||||||
raise SimplifiedToZero()
|
|
||||||
A_ubd_ret, b_ub_ret = Ab_ub_dt2d(eqns)
|
|
||||||
#print_eqns(A_ubd_ret, b_ub_ret, verbose=True, label='Debug')
|
|
||||||
return A_ubd_ret, b_ub_ret
|
|
||||||
|
|
||||||
|
|
||||||
def A_ubr_np2d(row):
|
|
||||||
'''Convert a single row'''
|
|
||||||
#d = {}
|
|
||||||
d = OrderedDict()
|
|
||||||
for coli, val in enumerate(row):
|
|
||||||
if val:
|
|
||||||
d[coli] = val
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def A_ub_np2d(A_ub):
|
|
||||||
'''Convert A_ub entries in numpy matrix to dictionary / sparse form'''
|
|
||||||
Adi = [None] * len(A_ub)
|
|
||||||
for i, row in enumerate(A_ub):
|
|
||||||
Adi[i] = A_ubr_np2d(row)
|
|
||||||
return Adi
|
|
||||||
|
|
||||||
|
|
||||||
def Ar_di2np(row_di, cols):
|
|
||||||
rownp = np.zeros(cols)
|
|
||||||
for coli, val in row_di.items():
|
|
||||||
# Sign inversion due to way solver works
|
|
||||||
rownp[coli] = val
|
|
||||||
return rownp
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE: sign inversion
|
|
||||||
def A_di2np(Adi, cols):
|
|
||||||
'''Convert A_ub entries in dictionary / sparse to numpy matrix form'''
|
|
||||||
return [Ar_di2np(row_di, cols) for row_di in Adi]
|
|
||||||
|
|
||||||
|
|
||||||
def Ar_ds2t(rowd):
|
|
||||||
'''Convert a dictionary row into a tuple with (column number, value) tuples'''
|
|
||||||
return tuple(sorted(rowd.items()))
|
|
||||||
|
|
||||||
|
|
||||||
def A_ubr_t2d(rowt):
|
|
||||||
'''Convert a dictionary row into a tuple with (column number, value) tuples'''
|
|
||||||
return OrderedDict(rowt)
|
|
||||||
|
|
||||||
|
|
||||||
def A_ub_d2t(A_ubd):
|
|
||||||
'''Convert rows as dicts to rows as tuples'''
|
|
||||||
return [Ar_ds2t(rowd) for rowd in A_ubd]
|
|
||||||
|
|
||||||
|
|
||||||
def A_ub_t2d(A_ubd):
|
|
||||||
'''Convert rows as tuples to rows as dicts'''
|
|
||||||
return [OrderedDict(rowt) for rowt in A_ubd]
|
|
||||||
|
|
||||||
|
|
||||||
def Ab_d2np(A_ubd, b_ub, names):
|
|
||||||
A_ub = A_di2np(A_ubd, len(names))
|
|
||||||
b_ub_inv = invb(b_ub)
|
|
||||||
return A_ub, b_ub_inv
|
|
||||||
|
|
||||||
|
|
||||||
def Ab_np2d(A_ub, b_ub_inv):
|
|
||||||
A_ubd = A_ub_np2d(A_ub)
|
|
||||||
b_ub = invb(b_ub_inv)
|
|
||||||
return A_ubd, b_ub
|
|
||||||
|
|
||||||
|
|
||||||
def sort_equations(Ads, b):
|
|
||||||
# Track rows with value column
|
|
||||||
# Hmm can't sort against np arrays
|
|
||||||
tosort = [(sorted(row.items()), rowb) for row, rowb in zip(Ads, b)]
|
|
||||||
#res = sorted(tosort, key=lambda e: e[0])
|
|
||||||
res = sorted(tosort)
|
|
||||||
A_ubtr, b_ubr = zip(*res)
|
|
||||||
return [OrderedDict(rowt) for rowt in A_ubtr], b_ubr
|
|
||||||
|
|
||||||
|
|
||||||
def col_dist(Ads, desc='of', names=[], lim=0):
|
|
||||||
'''print(frequency distribution of number of elements in a given row'''
|
|
||||||
rows = len(Ads)
|
|
||||||
cols = len(names)
|
|
||||||
|
|
||||||
fs = {}
|
|
||||||
for row in Ads:
|
|
||||||
this_cols = len(row)
|
|
||||||
fs[this_cols] = fs.get(this_cols, 0) + 1
|
|
||||||
|
|
||||||
print(
|
|
||||||
'Col count distribution (%s) for %dr x %dc w/ %d freqs' %
|
|
||||||
(desc, rows, cols, len(fs)))
|
|
||||||
prints = 0
|
|
||||||
for i, (k, v) in enumerate(sorted(fs.items())):
|
|
||||||
if lim == 0 or (lim and prints < lim or i == len(fs) - 1):
|
|
||||||
print(' %d: %d' % (k, v))
|
|
||||||
prints += 1
|
|
||||||
if lim and prints == lim:
|
|
||||||
print(' ...')
|
|
||||||
|
|
||||||
|
|
||||||
def name_dist(A_ubd, desc='of', names=[], lim=0):
|
|
||||||
'''print(frequency distribution of number of times an element appears'''
|
|
||||||
rows = len(A_ubd)
|
|
||||||
cols = len(names)
|
|
||||||
|
|
||||||
fs = {i: 0 for i in range(len(names))}
|
|
||||||
for row in A_ubd:
|
|
||||||
for k in row.keys():
|
|
||||||
fs[k] += 1
|
|
||||||
|
|
||||||
print('Name count distribution (%s) for %dr x %dc' % (desc, rows, cols))
|
|
||||||
prints = 0
|
|
||||||
for namei, name in enumerate(names):
|
|
||||||
if lim == 0 or (lim and prints < lim or namei == len(fs) - 1):
|
|
||||||
print(' %s: %d' % (name, fs[namei]))
|
|
||||||
prints += 1
|
|
||||||
if lim and prints == lim:
|
|
||||||
print(' ...')
|
|
||||||
|
|
||||||
fs2 = {}
|
|
||||||
for v in fs.values():
|
|
||||||
fs2[v] = fs2.get(v, 0) + 1
|
|
||||||
prints = 0
|
|
||||||
print('Distribution distribution (%d items)' % len(fs2))
|
|
||||||
for i, (k, v) in enumerate(sorted(fs2.items())):
|
|
||||||
if lim == 0 or (lim and prints < lim or i == len(fs2) - 1):
|
|
||||||
print(' %s: %s' % (k, v))
|
|
||||||
prints += 1
|
|
||||||
if lim and prints == lim:
|
|
||||||
print(' ...')
|
|
||||||
|
|
||||||
zeros = fs2.get(0, 0)
|
|
||||||
if zeros:
|
|
||||||
raise Exception("%d names without equation" % zeros)
|
|
||||||
|
|
||||||
|
|
||||||
def filter_ncols(A_ubd, b_ub, cols_min=0, cols_max=0):
|
|
||||||
'''Only keep equations with a few delay elements'''
|
|
||||||
A_ubd_ret = []
|
|
||||||
b_ub_ret = []
|
|
||||||
|
|
||||||
#print('Removing large rows')
|
|
||||||
for rowd, b in zip(A_ubd, b_ub):
|
|
||||||
if (not cols_min or len(rowd) >= cols_min) and (not cols_max or
|
|
||||||
len(rowd) <= cols_max):
|
|
||||||
A_ubd_ret.append(rowd)
|
|
||||||
b_ub_ret.append(b)
|
|
||||||
|
|
||||||
print(
|
|
||||||
'Filter ncols w/ %d <= cols <= %d: %d ==> %d rows' %
|
|
||||||
(cols_min, cols_max, len(b_ub), len(b_ub_ret)))
|
|
||||||
assert len(b_ub_ret)
|
|
||||||
return A_ubd_ret, b_ub_ret
|
|
||||||
|
|
||||||
|
|
||||||
def Ar_di2ds(rowA, names):
|
|
||||||
row = OrderedDict()
|
|
||||||
for k, v in rowA.items():
|
|
||||||
row[names[k]] = v
|
|
||||||
return row
|
|
||||||
|
|
||||||
|
|
||||||
def A_di2ds(Adi, names):
|
|
||||||
rows = []
|
|
||||||
for row_di in Adi:
|
|
||||||
rows.append(Ar_di2ds(row_di, names))
|
|
||||||
return rows
|
|
||||||
|
|
||||||
|
|
||||||
def Ar_ds2di(row_ds, names):
|
|
||||||
def keyi(name):
|
|
||||||
if name not in names:
|
|
||||||
names[name] = len(names)
|
|
||||||
return names[name]
|
|
||||||
|
|
||||||
row_di = OrderedDict()
|
|
||||||
for k, v in row_ds.items():
|
|
||||||
row_di[keyi(k)] = v
|
|
||||||
return row_di
|
|
||||||
|
|
||||||
|
|
||||||
def A_ds2di(rows):
|
|
||||||
names = OrderedDict()
|
|
||||||
|
|
||||||
A_ubd = []
|
|
||||||
for row_ds in rows:
|
|
||||||
A_ubd.append(Ar_ds2di(row_ds, names))
|
|
||||||
|
|
||||||
return list(names.keys()), A_ubd
|
|
||||||
|
|
||||||
|
|
||||||
def A_ds2np(Ads):
|
|
||||||
names, Adi = A_ds2di(Ads)
|
|
||||||
return names, A_di2np(Adi, len(names))
|
|
||||||
|
|
||||||
|
|
||||||
def loadc_Ads_mkb(fns, mkb, filt):
|
|
||||||
bs = []
|
|
||||||
Ads = []
|
|
||||||
for fn in fns:
|
|
||||||
with open(fn, 'r') as f:
|
|
||||||
# skip header
|
|
||||||
f.readline()
|
|
||||||
for l in f:
|
|
||||||
cols = l.split(',')
|
|
||||||
ico = bool(int(cols[0]))
|
|
||||||
corners = cols[1]
|
|
||||||
vars = cols[2:]
|
|
||||||
|
|
||||||
def mkcorner(bstr):
|
|
||||||
if bstr == 'None':
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return int(bstr)
|
|
||||||
|
|
||||||
corners = [mkcorner(corner) for corner in corners.split()]
|
|
||||||
|
|
||||||
def mkvar(x):
|
|
||||||
i, var = x.split()
|
|
||||||
return (var, int(i))
|
|
||||||
|
|
||||||
vars = OrderedDict([mkvar(var) for var in vars])
|
|
||||||
if not filt(ico, corners, vars):
|
|
||||||
continue
|
|
||||||
|
|
||||||
bs.append(mkb(corners))
|
|
||||||
Ads.append(vars)
|
|
||||||
|
|
||||||
return Ads, bs
|
|
||||||
|
|
||||||
|
|
||||||
def loadc_Ads_b(fns, corner, ico=None):
|
|
||||||
corner = corner or "slow_max"
|
|
||||||
corneri = corner_s2i[corner]
|
|
||||||
'''
|
|
||||||
if ico is not None:
|
|
||||||
filt = lambda ico_, corners, vars: ico_ == ico
|
|
||||||
else:
|
|
||||||
filt = lambda ico_, corners, vars: True
|
|
||||||
'''
|
|
||||||
assert ico is None, 'ICO filtering moved to higher levels'
|
|
||||||
filt = lambda ico_, corners, vars: True
|
|
||||||
|
|
||||||
def mkb(val):
|
|
||||||
return val[corneri]
|
|
||||||
|
|
||||||
return loadc_Ads_mkb(fns, mkb, filt)
|
|
||||||
|
|
||||||
|
|
||||||
def loadc_Ads_bs(fns, ico=None):
|
|
||||||
'''
|
|
||||||
if ico is not None:
|
|
||||||
filt = lambda ico_, corners, vars: ico_ == ico
|
|
||||||
else:
|
|
||||||
filt = lambda ico_, corners, vars: True
|
|
||||||
'''
|
|
||||||
assert ico is None, 'ICO filtering moved to higher levels'
|
|
||||||
filt = lambda ico_, corners, vars: True
|
|
||||||
|
|
||||||
def mkb(val):
|
|
||||||
return val
|
|
||||||
|
|
||||||
return loadc_Ads_mkb(fns, mkb, filt)
|
|
||||||
|
|
||||||
|
|
||||||
def loadc_Ads_raw(fns):
|
|
||||||
filt = lambda ico, corners, vars: True
|
|
||||||
|
|
||||||
def mkb(val):
|
|
||||||
return val
|
|
||||||
|
|
||||||
return loadc_Ads_mkb(fns, mkb, filt)
|
|
||||||
|
|
||||||
|
|
||||||
def index_names(Ads):
|
|
||||||
names = OrderedSet()
|
|
||||||
for row_ds in Ads:
|
|
||||||
for k1 in row_ds.keys():
|
|
||||||
names.add(k1)
|
|
||||||
return names
|
|
||||||
|
|
||||||
|
|
||||||
def load_sub(fn):
|
|
||||||
j = json.load(open(fn, 'r'))
|
|
||||||
|
|
||||||
for name, vals in sorted(j['subs'].items()):
|
|
||||||
for k, v in vals.items():
|
|
||||||
vals[k] = Fraction(v[0], v[1])
|
|
||||||
|
|
||||||
return j
|
|
||||||
|
|
||||||
|
|
||||||
def row_sub_vars(row, sub_json, strict=False, verbose=False):
|
|
||||||
if 0 and verbose:
|
|
||||||
print("")
|
|
||||||
print(row.items())
|
|
||||||
|
|
||||||
delvars = 0
|
|
||||||
for k in sub_json['zero_names']:
|
|
||||||
try:
|
|
||||||
del row[k]
|
|
||||||
delvars += 1
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if verbose:
|
|
||||||
print("Deleted %u variables" % delvars)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('Checking pivots')
|
|
||||||
print(sorted(row.items()))
|
|
||||||
for group, pivot in sorted(sub_json['pivots'].items()):
|
|
||||||
if pivot not in row:
|
|
||||||
continue
|
|
||||||
n = row[pivot]
|
|
||||||
print(' pivot %u %s' % (n, pivot))
|
|
||||||
|
|
||||||
#pivots = set(sub_json['pivots'].values()).intersection(row.keys())
|
|
||||||
for group, pivot in sorted(sub_json['pivots'].items()):
|
|
||||||
if pivot not in row:
|
|
||||||
continue
|
|
||||||
# take the sub out n times
|
|
||||||
# note constants may be negative
|
|
||||||
n = row[pivot]
|
|
||||||
if verbose:
|
|
||||||
print('pivot %i %s' % (n, pivot))
|
|
||||||
for subk, subv in sorted(sub_json['subs'][group].items()):
|
|
||||||
oldn = row.get(subk, type(subv)(0))
|
|
||||||
rown = -n * subv
|
|
||||||
rown += oldn
|
|
||||||
if verbose:
|
|
||||||
print(" %s: %d => %d" % (subk, oldn, rown))
|
|
||||||
if rown == 0:
|
|
||||||
# only becomes zero if didn't previously exist
|
|
||||||
del row[subk]
|
|
||||||
if verbose:
|
|
||||||
print(" del")
|
|
||||||
else:
|
|
||||||
row[subk] = rown
|
|
||||||
row[group] = n
|
|
||||||
assert pivot not in row
|
|
||||||
|
|
||||||
# after all constants are applied, the row should end up positive?
|
|
||||||
# numeric precision issues previously limited this
|
|
||||||
# Ex: AssertionError: ('PIP_BSW_2ELSING0', -2.220446049250313e-16)
|
|
||||||
if strict:
|
|
||||||
# verify no subs are left
|
|
||||||
for subs in sub_json['subs'].values():
|
|
||||||
for sub in subs:
|
|
||||||
assert sub not in row, 'non-pivot element after group sub %s' % sub
|
|
||||||
|
|
||||||
# Verify all constants are positive
|
|
||||||
for k, v in sorted(row.items()):
|
|
||||||
assert v > 0, (k, v)
|
|
||||||
|
|
||||||
|
|
||||||
def run_sub_json(Ads, sub_json, strict=False, verbose=False):
|
|
||||||
'''
|
|
||||||
strict: complain if a sub doesn't go in evenly
|
|
||||||
'''
|
|
||||||
|
|
||||||
nrows = 0
|
|
||||||
nsubs = 0
|
|
||||||
|
|
||||||
ncols_old = 0
|
|
||||||
ncols_new = 0
|
|
||||||
|
|
||||||
print('Subbing %u rows' % len(Ads))
|
|
||||||
prints = OrderedSet()
|
|
||||||
|
|
||||||
for rowi, row in enumerate(Ads):
|
|
||||||
if 0 and verbose:
|
|
||||||
print(row)
|
|
||||||
if verbose:
|
|
||||||
print('')
|
|
||||||
print('Row %u w/ %u elements' % (rowi, len(row)))
|
|
||||||
|
|
||||||
row_orig = dict(row)
|
|
||||||
row_sub_vars(row, sub_json, strict=strict, verbose=verbose)
|
|
||||||
nrows += 1
|
|
||||||
if row_orig != row:
|
|
||||||
nsubs += 1
|
|
||||||
if verbose:
|
|
||||||
rowt = Ar_ds2t(row)
|
|
||||||
if rowt not in prints:
|
|
||||||
print('row', row)
|
|
||||||
prints.add(rowt)
|
|
||||||
ncols_old += len(row_orig)
|
|
||||||
ncols_new += len(row)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print('')
|
|
||||||
|
|
||||||
print("Sub: %u / %u rows changed" % (nsubs, nrows))
|
|
||||||
print("Sub: %u => %u non-zero row cols" % (ncols_old, ncols_new))
|
|
||||||
|
|
||||||
|
|
||||||
def print_eqns(Ads, b, verbose=0, lim=3, label=''):
|
|
||||||
rows = len(b)
|
|
||||||
|
|
||||||
print('Sample equations (%s) from %d r' % (label, rows))
|
|
||||||
prints = 0
|
|
||||||
for rowi, row in enumerate(Ads):
|
|
||||||
if verbose or ((rowi < 10 or rowi % max(1, (rows / 20)) == 0) and
|
|
||||||
(not lim or prints < lim)):
|
|
||||||
line = ' EQN: p%u: ' % rowi
|
|
||||||
for k, v in sorted(row.items()):
|
|
||||||
line += '%u*t%s ' % (v, k)
|
|
||||||
line += '= %d' % b[rowi]
|
|
||||||
print(line)
|
|
||||||
prints += 1
|
|
||||||
|
|
||||||
|
|
||||||
def print_eqns_np(A_ub, b_ub, verbose=0):
|
|
||||||
Adi = A_ub_np2d(A_ub)
|
|
||||||
print_eqns(Adi, b_ub, verbose=verbose)
|
|
||||||
|
|
||||||
|
|
||||||
def Ads2bounds(Ads, bs):
|
|
||||||
ret = {}
|
|
||||||
for row_ds, row_bs in zip(Ads, bs):
|
|
||||||
assert len(row_ds) == 1
|
|
||||||
k, v = list(row_ds.items())[0]
|
|
||||||
assert v == 1
|
|
||||||
ret[k] = row_bs
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def instances(Ads):
|
|
||||||
ret = 0
|
|
||||||
for row_ds in Ads:
|
|
||||||
ret += sum(row_ds.values())
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def acorner2csv(b, corneri):
|
|
||||||
corners = ["None" for _ in range(4)]
|
|
||||||
corners[corneri] = str(b)
|
|
||||||
return ' '.join(corners)
|
|
||||||
|
|
||||||
|
|
||||||
def corners2csv(bs):
|
|
||||||
assert len(bs) == 4
|
|
||||||
corners = ["None" if b is None else str(b) for b in bs]
|
|
||||||
return ' '.join(corners)
|
|
||||||
|
|
||||||
|
|
||||||
def tilej_stats(tilej):
|
|
||||||
def tile_stats():
|
|
||||||
stats = {}
|
|
||||||
for etype in ('pips', 'wires'):
|
|
||||||
tm = stats.setdefault(etype, {})
|
|
||||||
tm['net'] = 0
|
|
||||||
tm['solved'] = [0, 0, 0, 0]
|
|
||||||
tm['covered'] = [0, 0, 0, 0]
|
|
||||||
|
|
||||||
for tile in tilej['tiles'].values():
|
|
||||||
for etype in ('pips', 'wires'):
|
|
||||||
pips = tile[etype]
|
|
||||||
for k, v in pips.items():
|
|
||||||
stats[etype]['net'] += 1
|
|
||||||
for i in range(4):
|
|
||||||
if pips[k][i]:
|
|
||||||
stats[etype]['solved'][i] += 1
|
|
||||||
if pips[k][i] is not None:
|
|
||||||
stats[etype]['covered'][i] += 1
|
|
||||||
return stats
|
|
||||||
|
|
||||||
def site_stats():
|
|
||||||
sitesj = tilej['sites']
|
|
||||||
ret = {
|
|
||||||
'sites': len(sitesj),
|
|
||||||
'wires': sum([len(wiresj) for wiresj in sitesj.values()]),
|
|
||||||
'wires_solved': {},
|
|
||||||
}
|
|
||||||
for corneri in corner_s2i.values():
|
|
||||||
ret['wires_solved'][corneri] = sum(
|
|
||||||
[
|
|
||||||
sum(
|
|
||||||
[
|
|
||||||
1 if site_wire[corneri] else 0
|
|
||||||
for site_wire in sitej.values()
|
|
||||||
])
|
|
||||||
for sitej in sitesj.values()
|
|
||||||
])
|
|
||||||
return ret
|
|
||||||
|
|
||||||
tstats = tile_stats()
|
|
||||||
sstats = site_stats()
|
|
||||||
|
|
||||||
for corner, corneri in corner_s2i.items():
|
|
||||||
print('Corner %s' % corner)
|
|
||||||
for etype in ('pips', 'wires'):
|
|
||||||
net = tstats[etype]['net']
|
|
||||||
solved = tstats[etype]['solved'][corneri]
|
|
||||||
covered = tstats[etype]['covered'][corneri]
|
|
||||||
print(
|
|
||||||
' %s: %u / %u solved, %u / %u covered' %
|
|
||||||
(etype, solved, net, covered, net))
|
|
||||||
print(
|
|
||||||
' sites: %u sites w/ %u solved / %u covered site wires' % (
|
|
||||||
sstats['sites'], sstats['wires_solved'][corneri],
|
|
||||||
sstats['wires']))
|
|
||||||
|
|
||||||
|
|
||||||
def load_bounds(bounds_csv, corner):
|
|
||||||
Ads, b = loadc_Ads_b([bounds_csv], corner)
|
|
||||||
return Ads2bounds(Ads, b)
|
|
||||||
|
|
@ -1,322 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import simplify_rows, print_eqns, print_eqns_np, sort_equations, col_dist, index_names
|
|
||||||
import numpy as np
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import copy
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
def lte_const(row_ref, row_cmp):
|
|
||||||
'''Return true if all constants are smaller magnitude in row_cmp than row_ref'''
|
|
||||||
#return False
|
|
||||||
for k, vc in row_cmp.items():
|
|
||||||
vr = row_ref.get(k, None)
|
|
||||||
# Not in reference?
|
|
||||||
if vr is None:
|
|
||||||
return False
|
|
||||||
if vr < vc:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def sub_rows(row_ref, row_cmp):
|
|
||||||
'''Do row_ref - row_cmp'''
|
|
||||||
#ret = {}
|
|
||||||
ret = OrderedDict()
|
|
||||||
ks = set(row_ref.keys())
|
|
||||||
ks.update(set(row_cmp.keys()))
|
|
||||||
for k in ks:
|
|
||||||
vr = row_ref.get(k, 0)
|
|
||||||
vc = row_cmp.get(k, 0)
|
|
||||||
res = vr - vc
|
|
||||||
if res:
|
|
||||||
ret[k] = res
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def derive_eq_by_row(Ads, b, verbose=0, col_lim=0, cmp_heuristic=True):
|
|
||||||
'''
|
|
||||||
Derive equations by subtracting whole rows
|
|
||||||
|
|
||||||
cmp_heuristic: drop large generated rows since these are unlikely to constrain the solution
|
|
||||||
|
|
||||||
|
|
||||||
Given equations like:
|
|
||||||
t0 >= 10
|
|
||||||
t0 + t1 >= 15
|
|
||||||
t0 + t1 + t2 >= 17
|
|
||||||
|
|
||||||
When I look at these, I think of a solution something like:
|
|
||||||
t0 = 10f
|
|
||||||
t1 = 5
|
|
||||||
t2 = 2
|
|
||||||
|
|
||||||
However, linprog tends to choose solutions like:
|
|
||||||
t0 = 17
|
|
||||||
t1 = 0
|
|
||||||
t2 = 0
|
|
||||||
|
|
||||||
To this end, add additional constraints by finding equations that are subsets of other equations
|
|
||||||
How to do this in a reasonable time span?
|
|
||||||
Also equations are sparse, which makes this harder to compute
|
|
||||||
'''
|
|
||||||
assert len(Ads) == len(b), 'Ads, b length mismatch'
|
|
||||||
rows = len(Ads)
|
|
||||||
|
|
||||||
# Index equations into hash maps so can lookup sparse elements quicker
|
|
||||||
assert len(Ads) == len(b)
|
|
||||||
Ads_ret = copy.copy(Ads)
|
|
||||||
assert len(Ads) == len(Ads_ret)
|
|
||||||
|
|
||||||
#print('Finding subsets')
|
|
||||||
ltes = 0
|
|
||||||
b_ret = list(b)
|
|
||||||
sys.stdout.write('Deriving rows (%u) ' % rows)
|
|
||||||
sys.stdout.flush()
|
|
||||||
progress = int(max(1, rows / 100))
|
|
||||||
b_warns = 0
|
|
||||||
for row_refi, row_ref in enumerate(Ads):
|
|
||||||
if row_refi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if col_lim and len(row_ref) > col_lim:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for row_cmpi, row_cmp in enumerate(Ads):
|
|
||||||
if row_refi == row_cmpi or col_lim and len(row_cmp) > col_lim:
|
|
||||||
continue
|
|
||||||
if not lte_const(row_ref, row_cmp):
|
|
||||||
continue
|
|
||||||
ltes += 1
|
|
||||||
if verbose:
|
|
||||||
print('')
|
|
||||||
print('match')
|
|
||||||
print(' ', row_ref, b[row_refi])
|
|
||||||
print(' ', row_cmp, b[row_cmpi])
|
|
||||||
|
|
||||||
# Reduce
|
|
||||||
row_new = sub_rows(row_ref, row_cmp)
|
|
||||||
|
|
||||||
# Did this actually significantly reduce the search space?
|
|
||||||
# should be a relatively small number of rows and be significantly smaller than at least one of them
|
|
||||||
def is_smaller_row():
|
|
||||||
# Keep any generally small rows
|
|
||||||
if len(row_new) <= 8:
|
|
||||||
return True
|
|
||||||
# And anything that reduced at least one row by half
|
|
||||||
# Ex: going from 120 and 100 element rows to a 20 element row
|
|
||||||
return len(row_new) <= len(row_cmp) / 2 or len(
|
|
||||||
row_new) <= len(row_ref) / 2
|
|
||||||
|
|
||||||
if cmp_heuristic and not is_smaller_row():
|
|
||||||
continue
|
|
||||||
b_new = b[row_refi] - b[row_cmpi]
|
|
||||||
# Definitely possible
|
|
||||||
# Maybe filter these out if they occur?
|
|
||||||
if verbose:
|
|
||||||
print(b_new)
|
|
||||||
# Also inverted sign
|
|
||||||
if b_new < 0:
|
|
||||||
if verbose:
|
|
||||||
print("Unexpected b")
|
|
||||||
b_warns += 1
|
|
||||||
continue
|
|
||||||
if verbose:
|
|
||||||
print('OK')
|
|
||||||
Ads_ret.append(row_new)
|
|
||||||
b_ret.append(b_new)
|
|
||||||
print(' done')
|
|
||||||
|
|
||||||
#A_ub_ret = A_di2np(Ads2, cols=cols)
|
|
||||||
print(
|
|
||||||
'Derive row: %d => %d rows using %d lte' % (len(b), len(b_ret), ltes))
|
|
||||||
print('Dropped %u invalid equations' % b_warns)
|
|
||||||
assert len(Ads_ret) == len(b_ret)
|
|
||||||
return Ads_ret, b_ret
|
|
||||||
|
|
||||||
|
|
||||||
def derive_eq_by_col(Ads, b_ub, verbose=0, keep_orig=True):
|
|
||||||
'''
|
|
||||||
Derive equations by subtracting out all bounded constants (ie "known" columns)
|
|
||||||
XXX: is this now redundant with derive_row?
|
|
||||||
Seems like a degenerate case, although maybe quicker
|
|
||||||
'''
|
|
||||||
rows = len(Ads)
|
|
||||||
|
|
||||||
# Index equations with a single constraint
|
|
||||||
knowns = {}
|
|
||||||
sys.stdout.write('Derive col indexing ')
|
|
||||||
sys.stdout.flush()
|
|
||||||
progress = max(1, rows / 100)
|
|
||||||
for row_refi, row_refd in enumerate(Ads):
|
|
||||||
if row_refi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if len(row_refd) == 1:
|
|
||||||
k, v = list(row_refd.items())[0]
|
|
||||||
# assume simplify_equations handles de-duplicating
|
|
||||||
new_b = 1.0 * b_ub[row_refi] / v
|
|
||||||
assert k not in knowns, "Got new %s w/ val %u, old val %u" % (
|
|
||||||
k, new_b, knowns[k])
|
|
||||||
knowns[k] = new_b
|
|
||||||
print(' done')
|
|
||||||
#knowns_set = set(knowns.keys())
|
|
||||||
print('%d constrained' % len(knowns))
|
|
||||||
'''
|
|
||||||
Now see what we can do
|
|
||||||
Rows that are already constrained: eliminate
|
|
||||||
TODO: maybe keep these if this would violate their constraint
|
|
||||||
Otherwise eliminate the original row and generate a simplified result now
|
|
||||||
'''
|
|
||||||
b_ret = []
|
|
||||||
Ads_ret = []
|
|
||||||
sys.stdout.write('Derive col main ')
|
|
||||||
sys.stdout.flush()
|
|
||||||
progress = max(1, rows / 100)
|
|
||||||
for row_refi, row_refd in enumerate(Ads):
|
|
||||||
if row_refi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
b_ref = b_ub[row_refi]
|
|
||||||
if keep_orig:
|
|
||||||
Ads_ret.append(row_refd)
|
|
||||||
b_ret.append(b_ref)
|
|
||||||
|
|
||||||
# Reduce as much as possible
|
|
||||||
#row_new = {}
|
|
||||||
row_new = OrderedDict()
|
|
||||||
b_new = b_ref
|
|
||||||
# Copy over single entries
|
|
||||||
if len(row_refd) == 1:
|
|
||||||
row_new = row_refd
|
|
||||||
else:
|
|
||||||
for k, v in row_refd.items():
|
|
||||||
if k in knowns:
|
|
||||||
# Remove column and take out corresponding delay
|
|
||||||
b_new -= v * knowns[k]
|
|
||||||
# Copy over
|
|
||||||
else:
|
|
||||||
row_new[k] = v
|
|
||||||
|
|
||||||
# Possibly reduced all usable contants out
|
|
||||||
if len(row_new) == 0:
|
|
||||||
continue
|
|
||||||
# invalid?
|
|
||||||
if b_new < 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not keep_orig or row_new != row_refd:
|
|
||||||
Ads_ret.append(row_new)
|
|
||||||
b_ret.append(b_new)
|
|
||||||
print(' done')
|
|
||||||
|
|
||||||
print('Derive col: %d => %d rows' % (len(b_ub), len(b_ret)))
|
|
||||||
return Ads_ret, b_ret
|
|
||||||
|
|
||||||
|
|
||||||
# iteratively increasing column limit until all columns are added
|
|
||||||
def massage_equations(
|
|
||||||
Ads, b, verbose=False, corner=None, iter_lim=1, col_lim=100000):
|
|
||||||
#col_lim = 15
|
|
||||||
'''
|
|
||||||
Subtract equations from each other to generate additional constraints
|
|
||||||
Helps provide additional guidance to solver for realistic delays
|
|
||||||
|
|
||||||
Ex: given:
|
|
||||||
a >= 10
|
|
||||||
a + b >= 100
|
|
||||||
A valid solution is:
|
|
||||||
a = 100
|
|
||||||
However, a better solution is something like
|
|
||||||
a = 10
|
|
||||||
b = 90
|
|
||||||
This creates derived constraints to provide more realistic results
|
|
||||||
|
|
||||||
Equation pipeline
|
|
||||||
Some operations may generate new equations
|
|
||||||
Simplify after these to avoid unnecessary overhead on redundant constraints
|
|
||||||
Similarly some operations may eliminate equations, potentially eliminating a column (ie variable)
|
|
||||||
Remove these columns as necessary to speed up solving
|
|
||||||
'''
|
|
||||||
|
|
||||||
assert len(Ads) == len(b), 'Ads, b length mismatch'
|
|
||||||
|
|
||||||
def check_cols():
|
|
||||||
assert len(index_names(Ads)) == cols
|
|
||||||
|
|
||||||
def debug(what):
|
|
||||||
check_cols()
|
|
||||||
if 1 or verbose:
|
|
||||||
print('')
|
|
||||||
print_eqns(Ads, b, verbose=verbose, label=what, lim=20)
|
|
||||||
col_dist(Ads, what)
|
|
||||||
#check_feasible_d(Ads, b)
|
|
||||||
|
|
||||||
# Try to (intelligently) subtract equations to generate additional constraints
|
|
||||||
# This helps avoid putting all delay in a single shared variable
|
|
||||||
dstart = len(b)
|
|
||||||
cols = len(index_names(Ads))
|
|
||||||
|
|
||||||
# Each iteration one more column is allowed until all columns are included
|
|
||||||
# (and the system is stable)
|
|
||||||
di = 0
|
|
||||||
while True:
|
|
||||||
print
|
|
||||||
n_orig = len(b)
|
|
||||||
|
|
||||||
print('Loop %d, lim %d' % (di + 1, col_lim))
|
|
||||||
# Meat of the operation
|
|
||||||
Ads, b = derive_eq_by_row(Ads, b, col_lim=col_lim, cmp_heuristic=True)
|
|
||||||
debug("der_rows")
|
|
||||||
# Run another simplify pass since new equations may have overlap with original
|
|
||||||
Ads, b = simplify_rows(Ads, b, corner=corner)
|
|
||||||
print('Derive row: %d => %d equations' % (n_orig, len(b)))
|
|
||||||
debug("der_rows simp")
|
|
||||||
|
|
||||||
# derive_cols is mostly degenerate case of derive_rows
|
|
||||||
# however, it will sub out single variables a lot faster if there are a lot of them
|
|
||||||
# linear vs above quadratic, might as well keep it in
|
|
||||||
if 1:
|
|
||||||
n_orig2 = len(b)
|
|
||||||
# Meat of the operation
|
|
||||||
Ads, b = derive_eq_by_col(Ads, b)
|
|
||||||
debug("der_cols")
|
|
||||||
# Run another simplify pass since new equations may have overlap with original
|
|
||||||
Ads, b = simplify_rows(Ads, b, corner=corner)
|
|
||||||
print(
|
|
||||||
'Derive col %d: %d => %d equations' %
|
|
||||||
(di + 1, n_orig2, len(b)))
|
|
||||||
debug("der_cols simp")
|
|
||||||
|
|
||||||
# Doesn't help computation, but helps debugging
|
|
||||||
Ads, b = sort_equations(Ads, b)
|
|
||||||
debug("loop done")
|
|
||||||
col_dist(Ads, 'derive done iter %d, lim %d' % (di, col_lim), lim=12)
|
|
||||||
|
|
||||||
rows = len(Ads)
|
|
||||||
di += 1
|
|
||||||
dend = len(b)
|
|
||||||
# possible that a new equation was generated and taken away, but close enough
|
|
||||||
if n_orig == len(b) and col_lim >= cols or di >= iter_lim:
|
|
||||||
break
|
|
||||||
col_lim += col_lim / 5
|
|
||||||
|
|
||||||
print('')
|
|
||||||
print('Derive net: %d => %d' % (dstart, dend))
|
|
||||||
print('')
|
|
||||||
# Was experimentting to see how much the higher order columns really help
|
|
||||||
|
|
||||||
# Helps debug readability
|
|
||||||
Ads, b = sort_equations(Ads, b)
|
|
||||||
debug("final (sorted)")
|
|
||||||
print('')
|
|
||||||
print('Massage final: %d => %d rows' % (dstart, dend))
|
|
||||||
cols_end = len(index_names(Ads))
|
|
||||||
print('Massage final: %d => %d cols' % (cols, cols_end))
|
|
||||||
assert cols_end == cols
|
|
||||||
return Ads, b
|
|
||||||
|
|
@ -1,237 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import simplify_rows, loadc_Ads_b, index_names, A_ds2np, run_sub_json, print_eqns, Ads2bounds, instances, SimplifiedToZero, allow_zero_eqns, corner_s2i, acorner2csv
|
|
||||||
from timfuz_massage import massage_equations
|
|
||||||
import numpy as np
|
|
||||||
import sys
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
def check_feasible(A_ub, b_ub):
|
|
||||||
'''
|
|
||||||
Put large timing constants into the equations
|
|
||||||
See if that would solve it
|
|
||||||
|
|
||||||
Its having trouble giving me solutions as this gets bigger
|
|
||||||
Make a terrible baseline guess to confirm we aren't doing something bad
|
|
||||||
'''
|
|
||||||
|
|
||||||
sys.stdout.write('Check feasible ')
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
rows = len(b_ub)
|
|
||||||
cols = len(A_ub[0])
|
|
||||||
|
|
||||||
progress = max(1, rows / 100)
|
|
||||||
'''
|
|
||||||
Delays should be in order of ns, so a 10 ns delay should be way above what anything should be
|
|
||||||
Series can have several hundred delay elements
|
|
||||||
Max delay in ballpark
|
|
||||||
'''
|
|
||||||
xs = [1e9 for _i in range(cols)]
|
|
||||||
|
|
||||||
# FIXME: use the correct np function to do this for me
|
|
||||||
# Verify bounds
|
|
||||||
#b_res = np.matmul(A_ub, xs)
|
|
||||||
#print(type(A_ub), type(xs)
|
|
||||||
#A_ub = np.array(A_ub)
|
|
||||||
#xs = np.array(xs)
|
|
||||||
#b_res = np.matmul(A_ub, xs)
|
|
||||||
def my_mul(A_ub, xs):
|
|
||||||
#print('cols', cols
|
|
||||||
#print('rows', rows
|
|
||||||
ret = [None] * rows
|
|
||||||
for row in range(rows):
|
|
||||||
this = 0
|
|
||||||
for col in range(cols):
|
|
||||||
this += A_ub[row][col] * xs[col]
|
|
||||||
ret[row] = this
|
|
||||||
return ret
|
|
||||||
|
|
||||||
b_res = my_mul(A_ub, xs)
|
|
||||||
|
|
||||||
# Verify bound was respected
|
|
||||||
for rowi, (this_b, this_b_ub) in enumerate(zip(b_res, b_ub)):
|
|
||||||
if rowi % progress == 0:
|
|
||||||
sys.stdout.write('.')
|
|
||||||
sys.stdout.flush()
|
|
||||||
if this_b >= this_b_ub or this_b > 0:
|
|
||||||
print(
|
|
||||||
'% 4d Want res % 10.1f <= % 10.1f <= 0' %
|
|
||||||
(rowi, this_b, this_b_ub))
|
|
||||||
raise Exception("Bad ")
|
|
||||||
print(' done')
|
|
||||||
|
|
||||||
|
|
||||||
def filter_bounds(Ads, b, bounds, corner):
|
|
||||||
'''
|
|
||||||
Given min variable delays, remove rows that won't constrain solution
|
|
||||||
Ex for max corner:
|
|
||||||
Given bounds:
|
|
||||||
a >= 10
|
|
||||||
b >= 1
|
|
||||||
c >= 0
|
|
||||||
Given equations:
|
|
||||||
a + b >= 10
|
|
||||||
a + c >= 100
|
|
||||||
The first equation is already satisfied
|
|
||||||
However, the second needs either an increase in a or an increase in c '''
|
|
||||||
|
|
||||||
if 'max' in corner:
|
|
||||||
# Keep delays possibly larger than current bound
|
|
||||||
def keep(row_b, est):
|
|
||||||
return row_b > est
|
|
||||||
|
|
||||||
T_UNK = 0
|
|
||||||
elif 'min' in corner:
|
|
||||||
# Keep delays possibly smaller than current bound
|
|
||||||
def keep(row_b, est):
|
|
||||||
return row_b < est
|
|
||||||
|
|
||||||
T_UNK = 1e9
|
|
||||||
else:
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
ret_Ads = []
|
|
||||||
ret_b = []
|
|
||||||
unknowns = set()
|
|
||||||
for row_ds, row_b in zip(Ads, b):
|
|
||||||
# some variables get estimated at 0
|
|
||||||
def getvar(k):
|
|
||||||
#return bounds.get(k, T_UNK)
|
|
||||||
ret = bounds.get(k, None)
|
|
||||||
if ret is not None:
|
|
||||||
return ret
|
|
||||||
unknowns.add(k)
|
|
||||||
return T_UNK
|
|
||||||
|
|
||||||
est = sum([getvar(k) * v for k, v in row_ds.items()])
|
|
||||||
# will this row potentially constrain us more?
|
|
||||||
if keep(row_b, est):
|
|
||||||
ret_Ads.append(row_ds)
|
|
||||||
ret_b.append(row_b)
|
|
||||||
if len(unknowns):
|
|
||||||
print('WARNING: %u encountered undefined bounds' % len(unknowns))
|
|
||||||
return ret_Ads, ret_b
|
|
||||||
|
|
||||||
|
|
||||||
def solve_save(outfn, xvals, names, corner, save_zero=True, verbose=False):
|
|
||||||
# ballpark minimum actual observed delay is around 7 (carry chain)
|
|
||||||
# anything less than one is probably a solver artifact
|
|
||||||
delta = 0.5
|
|
||||||
corneri = corner_s2i[corner]
|
|
||||||
|
|
||||||
roundf = {
|
|
||||||
'fast_max': math.ceil,
|
|
||||||
'fast_min': math.floor,
|
|
||||||
'slow_max': math.ceil,
|
|
||||||
'slow_min': math.floor,
|
|
||||||
}[corner]
|
|
||||||
|
|
||||||
print('Writing results')
|
|
||||||
zeros = 0
|
|
||||||
with open(outfn, 'w') as fout:
|
|
||||||
# write as one variable per line
|
|
||||||
# this natively forms a bound if fed into linprog solver
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for xval, name in zip(xvals, names):
|
|
||||||
row_ico = 1
|
|
||||||
|
|
||||||
if xval < delta:
|
|
||||||
if verbose:
|
|
||||||
print('WARNING: near 0 delay on %s: %0.6f' % (name, xval))
|
|
||||||
zeros += 1
|
|
||||||
if not save_zero:
|
|
||||||
continue
|
|
||||||
items = [str(row_ico), acorner2csv(roundf(xval), corneri)]
|
|
||||||
items.append('%u %s' % (1, name))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
nonzeros = len(names) - zeros
|
|
||||||
print(
|
|
||||||
'Wrote: %u / %u constrained delays, %u zeros' %
|
|
||||||
(nonzeros, len(names), zeros))
|
|
||||||
# max only...min corner seems to like 0
|
|
||||||
# see https://github.com/SymbiFlow/prjxray/issues/136
|
|
||||||
if 'max' in corner:
|
|
||||||
assert nonzeros, 'Failed to estimate delay'
|
|
||||||
|
|
||||||
|
|
||||||
def run(
|
|
||||||
fns_in,
|
|
||||||
corner,
|
|
||||||
run_corner,
|
|
||||||
sub_json=None,
|
|
||||||
bounds_csv=None,
|
|
||||||
dedup=True,
|
|
||||||
massage=False,
|
|
||||||
outfn=None,
|
|
||||||
verbose=False,
|
|
||||||
**kwargs):
|
|
||||||
print('Loading data')
|
|
||||||
Ads, b = loadc_Ads_b(fns_in, corner)
|
|
||||||
|
|
||||||
# Remove duplicate rows
|
|
||||||
# is this necessary?
|
|
||||||
# maybe better to just add them into the matrix directly
|
|
||||||
if dedup:
|
|
||||||
oldn = len(Ads)
|
|
||||||
iold = instances(Ads)
|
|
||||||
Ads, b = simplify_rows(Ads, b, corner=corner)
|
|
||||||
print('Simplify %u => %u rows' % (oldn, len(Ads)))
|
|
||||||
print('Simplify %u => %u instances' % (iold, instances(Ads)))
|
|
||||||
|
|
||||||
if sub_json:
|
|
||||||
print('Sub: %u rows' % len(Ads))
|
|
||||||
iold = instances(Ads)
|
|
||||||
names_old = index_names(Ads)
|
|
||||||
run_sub_json(Ads, sub_json, verbose=verbose)
|
|
||||||
names = index_names(Ads)
|
|
||||||
print("Sub: %u => %u names" % (len(names_old), len(names)))
|
|
||||||
print('Sub: %u => %u instances' % (iold, instances(Ads)))
|
|
||||||
else:
|
|
||||||
names = index_names(Ads)
|
|
||||||
'''
|
|
||||||
Substitution .csv
|
|
||||||
Special .csv containing one variable per line
|
|
||||||
Used primarily for multiple optimization passes, such as different algorithms or additional constraints
|
|
||||||
'''
|
|
||||||
if bounds_csv:
|
|
||||||
Ads2, b2 = loadc_Ads_b([bounds_csv], corner)
|
|
||||||
bounds = Ads2bounds(Ads2, b2)
|
|
||||||
assert len(bounds), 'Failed to load bounds'
|
|
||||||
rows_old = len(Ads)
|
|
||||||
Ads, b = filter_bounds(Ads, b, bounds, corner)
|
|
||||||
print(
|
|
||||||
'Filter bounds: %s => %s + %s rows' %
|
|
||||||
(rows_old, len(Ads), len(Ads2)))
|
|
||||||
Ads = Ads + Ads2
|
|
||||||
b = b + b2
|
|
||||||
assert len(Ads) or allow_zero_eqns()
|
|
||||||
assert len(Ads) == len(b), 'Ads, b length mismatch'
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print
|
|
||||||
print_eqns(Ads, b, verbose=verbose)
|
|
||||||
|
|
||||||
#print
|
|
||||||
#col_dist(A_ubd, 'final', names)
|
|
||||||
if massage:
|
|
||||||
try:
|
|
||||||
Ads, b = massage_equations(Ads, b, corner=corner)
|
|
||||||
except SimplifiedToZero:
|
|
||||||
if not allow_zero_eqns():
|
|
||||||
raise
|
|
||||||
print('WARNING: simplified to zero equations')
|
|
||||||
Ads = []
|
|
||||||
b = []
|
|
||||||
|
|
||||||
print('Converting to numpy...')
|
|
||||||
names, Anp = A_ds2np(Ads)
|
|
||||||
run_corner(
|
|
||||||
Anp,
|
|
||||||
np.asarray(b),
|
|
||||||
names,
|
|
||||||
corner,
|
|
||||||
outfn=outfn,
|
|
||||||
verbose=verbose,
|
|
||||||
**kwargs)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
all: build/timgrid.json
|
|
||||||
|
|
||||||
build/timgrid.txt: generate.tcl
|
|
||||||
mkdir -p build
|
|
||||||
cd build && ${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
|
|
||||||
build/timgrid.json: build/timgrid.txt
|
|
||||||
cd build && python3 ../tile_txt2json.py --speed-json ../../speed/build/speed.json timgrid.txt timgrid-s.json
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf build
|
|
||||||
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/bash -x
|
|
||||||
|
|
||||||
source ${XRAY_GENHEADER}
|
|
||||||
|
|
||||||
${XRAY_VIVADO} -mode batch -source ../generate.tcl
|
|
||||||
|
|
||||||
for x in design*.bit; do
|
|
||||||
${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x
|
|
||||||
done
|
|
||||||
|
|
||||||
for x in design_*.bits; do
|
|
||||||
diff -u design.bits $x | grep '^[-+]bit' > ${x%.bits}.delta
|
|
||||||
done
|
|
||||||
|
|
||||||
python3 ../generate.py design_*.delta > tilegrid.json
|
|
||||||
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
proc build_project {} {
|
|
||||||
if 0 {
|
|
||||||
set grid_min_x -1
|
|
||||||
set grid_max_x -1
|
|
||||||
set grid_min_y -1
|
|
||||||
set grid_max_y -1
|
|
||||||
} {
|
|
||||||
set grid_min_x $::env(XRAY_ROI_GRID_X1)
|
|
||||||
set grid_max_x $::env(XRAY_ROI_GRID_X2)
|
|
||||||
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
|
|
||||||
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
|
|
||||||
}
|
|
||||||
|
|
||||||
create_project -force -part $::env(XRAY_PART) design design
|
|
||||||
|
|
||||||
read_verilog ../top.v
|
|
||||||
synth_design -top top
|
|
||||||
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do]
|
|
||||||
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb]
|
|
||||||
|
|
||||||
create_pblock roi
|
|
||||||
add_cells_to_pblock [get_pblocks roi] [get_cells roi]
|
|
||||||
resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)"
|
|
||||||
|
|
||||||
set_property CFGBVS VCCO [current_design]
|
|
||||||
set_property CONFIG_VOLTAGE 3.3 [current_design]
|
|
||||||
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
|
|
||||||
set_param tcl.collectionResultDisplayLimit 0
|
|
||||||
|
|
||||||
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
|
|
||||||
|
|
||||||
set luts [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT]
|
|
||||||
set selected_luts {}
|
|
||||||
set lut_index 0
|
|
||||||
|
|
||||||
# LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per column)
|
|
||||||
# Also, if GRID_MIN/MAX is not defined, automatically create it based on used CLBs
|
|
||||||
# See caveat in README on automatic creation
|
|
||||||
foreach lut $luts {
|
|
||||||
set tile [get_tile -of_objects $lut]
|
|
||||||
set grid_x [get_property GRID_POINT_X $tile]
|
|
||||||
set grid_y [get_property GRID_POINT_Y $tile]
|
|
||||||
|
|
||||||
if [expr $grid_min_x < 0 || $grid_x < $grid_min_x] {set grid_min_x $grid_x}
|
|
||||||
if [expr $grid_max_x < 0 || $grid_x > $grid_max_x] {set grid_max_x $grid_x}
|
|
||||||
|
|
||||||
if [expr $grid_min_y < 0 || $grid_y < $grid_min_y] {set grid_min_y $grid_y}
|
|
||||||
if [expr $grid_max_y < 0 || $grid_y > $grid_max_y] {set grid_max_y $grid_y}
|
|
||||||
|
|
||||||
# 50 per column => 50, 100, 150, etc
|
|
||||||
if [regexp "Y(0|[0-9]*[05]0)/" $lut] {
|
|
||||||
set cell [get_cells roi/is[$lut_index].lut]
|
|
||||||
set_property LOC [get_sites -of_objects $lut] $cell
|
|
||||||
set lut_index [expr $lut_index + 1]
|
|
||||||
lappend selected_luts $lut
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
place_design
|
|
||||||
route_design
|
|
||||||
|
|
||||||
write_checkpoint -force design.dcp
|
|
||||||
write_bitstream -force design.bit
|
|
||||||
}
|
|
||||||
|
|
||||||
proc write_data {} {
|
|
||||||
if 0 {
|
|
||||||
set grid_min_x -1
|
|
||||||
set grid_max_x -1
|
|
||||||
set grid_min_y -1
|
|
||||||
set grid_max_y -1
|
|
||||||
} {
|
|
||||||
set grid_min_x $::env(XRAY_ROI_GRID_X1)
|
|
||||||
set grid_max_x $::env(XRAY_ROI_GRID_X2)
|
|
||||||
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
|
|
||||||
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all tiles in ROI, ie not just the selected LUTs
|
|
||||||
set tiles [get_tiles -filter "GRID_POINT_X >= $grid_min_x && GRID_POINT_X <= $grid_max_x && GRID_POINT_Y >= $grid_min_y && GRID_POINT_Y <= $grid_max_y"]
|
|
||||||
|
|
||||||
# Write tiles.txt with site metadata
|
|
||||||
set fp [open "timgrid.txt" w]
|
|
||||||
foreach tile $tiles {
|
|
||||||
set type [get_property TYPE $tile]
|
|
||||||
set grid_x [get_property GRID_POINT_X $tile]
|
|
||||||
set grid_y [get_property GRID_POINT_Y $tile]
|
|
||||||
|
|
||||||
set items {}
|
|
||||||
|
|
||||||
set wires [get_wires -of_objects $tile]
|
|
||||||
if [llength $wires] {
|
|
||||||
foreach wire $wires {
|
|
||||||
set name [get_property NAME $wire]
|
|
||||||
set speed_index [get_property SPEED_INDEX $wire]
|
|
||||||
lappend items wire $name $speed_index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set pips [get_pips -of_objects $tile]
|
|
||||||
if [llength $pips] {
|
|
||||||
foreach pip $pips {
|
|
||||||
set name [get_property NAME $pip]
|
|
||||||
set speed_index [get_property SPEED_INDEX $pip]
|
|
||||||
lappend items pip $name $speed_index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
puts $fp "$type $tile $grid_x $grid_y $items"
|
|
||||||
}
|
|
||||||
close $fp
|
|
||||||
}
|
|
||||||
|
|
||||||
build_project
|
|
||||||
write_data
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
SI_NONE = 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
def load_speed_json(f):
|
|
||||||
j = json.load(f)
|
|
||||||
# Index speed indexes to names
|
|
||||||
speed_i2s = {}
|
|
||||||
for k, v in j['speed_model'].items():
|
|
||||||
i = v['speed_index']
|
|
||||||
if i != SI_NONE:
|
|
||||||
speed_i2s[i] = k
|
|
||||||
return j, speed_i2s
|
|
||||||
|
|
||||||
|
|
||||||
def gen_tiles(fnin, speed_i2s):
|
|
||||||
for l in open(fnin):
|
|
||||||
# lappend items pip $name $speed_index
|
|
||||||
# puts $fp "$type $tile $grid_x $grid_y $items"
|
|
||||||
parts = l.strip().split()
|
|
||||||
tile_type, tile_name, grid_x, grid_y = parts[0:4]
|
|
||||||
grid_x, grid_y = int(grid_x), int(grid_y)
|
|
||||||
tuples = parts[4:]
|
|
||||||
assert len(tuples) % 3 == 0
|
|
||||||
pips = {}
|
|
||||||
wires = {}
|
|
||||||
for i in range(0, len(tuples), 3):
|
|
||||||
ttype, name, speed_index = tuples[i:i + 3]
|
|
||||||
name_local = name.split('/')[1]
|
|
||||||
{
|
|
||||||
'pip': pips,
|
|
||||||
'wire': wires,
|
|
||||||
}[ttype][name_local] = speed_i2s[int(speed_index)]
|
|
||||||
yield (tile_type, tile_name, grid_x, grid_y, pips, wires)
|
|
||||||
|
|
||||||
|
|
||||||
def run(fnin, fnout, speed_json_fn, verbose=False):
|
|
||||||
speedj, speed_i2s = load_speed_json(open(speed_json_fn, 'r'))
|
|
||||||
|
|
||||||
tiles = {}
|
|
||||||
for tile in gen_tiles(fnin, speed_i2s):
|
|
||||||
(tile_type, tile_name, grid_x, grid_y, pips, wires) = tile
|
|
||||||
this_dat = {'pips': pips, 'wires': wires}
|
|
||||||
if tile_type not in tiles:
|
|
||||||
tiles[tile_type] = this_dat
|
|
||||||
else:
|
|
||||||
if tiles[tile_type] != this_dat:
|
|
||||||
print(tile_name, tile_type)
|
|
||||||
print(this_dat)
|
|
||||||
print(tiles[tile_type])
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
j = {'tiles': tiles}
|
|
||||||
json.dump(
|
|
||||||
j, open(fnout, 'w'), sort_keys=True, indent=4, separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Solve timing solution')
|
|
||||||
parser.add_argument(
|
|
||||||
'--speed-json',
|
|
||||||
default='../../speed/build/speed.json',
|
|
||||||
help='Provides speed index to name translation')
|
|
||||||
parser.add_argument('fnin', default=None, help='input tcl output .txt')
|
|
||||||
parser.add_argument('fnout', default=None, help='output .json')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(args.fnin, args.fnout, speed_json_fn=args.speed_json, verbose=False)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
//Need at least one LUT per frame base address we want
|
|
||||||
`define N 100
|
|
||||||
|
|
||||||
module top(input clk, stb, di, output do);
|
|
||||||
localparam integer DIN_N = 6;
|
|
||||||
localparam integer DOUT_N = `N;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
|
|
||||||
roi roi (
|
|
||||||
.clk(clk),
|
|
||||||
.din(din),
|
|
||||||
.dout(dout)
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module roi(input clk, input [5:0] din, output [`N-1:0] dout);
|
|
||||||
genvar i;
|
|
||||||
generate
|
|
||||||
for (i = 0; i < `N; i = i+1) begin:is
|
|
||||||
LUT6 #(
|
|
||||||
.INIT(64'h8000_0000_0000_0001 + (i << 16))
|
|
||||||
) lut (
|
|
||||||
.I0(din[0]),
|
|
||||||
.I1(din[1]),
|
|
||||||
.I2(din[2]),
|
|
||||||
.I3(din[3]),
|
|
||||||
.I4(din[4]),
|
|
||||||
.I5(din[5]),
|
|
||||||
.O(dout[i])
|
|
||||||
);
|
|
||||||
end
|
|
||||||
endgenerate
|
|
||||||
endmodule
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
`define N 100
|
|
||||||
|
|
||||||
module top(input clk, stb, di, output do);
|
|
||||||
localparam integer DIN_N = 8;
|
|
||||||
localparam integer DOUT_N = `N;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din;
|
|
||||||
wire [DOUT_N-1:0] dout;
|
|
||||||
|
|
||||||
reg [DIN_N-1:0] din_shr;
|
|
||||||
reg [DOUT_N-1:0] dout_shr;
|
|
||||||
|
|
||||||
always @(posedge clk) begin
|
|
||||||
din_shr <= {din_shr, di};
|
|
||||||
dout_shr <= {dout_shr, din_shr[DIN_N-1]};
|
|
||||||
if (stb) begin
|
|
||||||
din <= din_shr;
|
|
||||||
dout_shr <= dout;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assign do = dout_shr[DOUT_N-1];
|
|
||||||
|
|
||||||
roi roi (
|
|
||||||
.clk(clk),
|
|
||||||
.din(din),
|
|
||||||
.dout(dout)
|
|
||||||
);
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
module roi(input clk, input [7:0] din, output [`N-1:0] dout);
|
|
||||||
genvar i;
|
|
||||||
generate
|
|
||||||
for (i = 0; i < `N; i = i+1) begin:is
|
|
||||||
(* KEEP, DONT_TOUCH *)
|
|
||||||
RAMB36E1 #(.INIT_00(256'h0000000000000000000000000000000000000000000000000000000000000000)) ram (
|
|
||||||
.CLKARDCLK(din[0]),
|
|
||||||
.CLKBWRCLK(din[1]),
|
|
||||||
.ENARDEN(din[2]),
|
|
||||||
.ENBWREN(din[3]),
|
|
||||||
.REGCEAREGCE(din[4]),
|
|
||||||
.REGCEB(din[5]),
|
|
||||||
.RSTRAMARSTRAM(din[6]),
|
|
||||||
.RSTRAMB(din[7]),
|
|
||||||
.RSTREGARSTREG(din[0]),
|
|
||||||
.RSTREGB(din[1]),
|
|
||||||
.ADDRARDADDR(din[2]),
|
|
||||||
.ADDRBWRADDR(din[3]),
|
|
||||||
.DIADI(din[4]),
|
|
||||||
.DIBDI(din[5]),
|
|
||||||
.DIPADIP(din[6]),
|
|
||||||
.DIPBDIP(din[7]),
|
|
||||||
.WEA(din[0]),
|
|
||||||
.WEBWE(din[1]),
|
|
||||||
.DOADO(dout[0]),
|
|
||||||
.DOBDO(),
|
|
||||||
.DOPADOP(),
|
|
||||||
.DOPBDOP());
|
|
||||||
end
|
|
||||||
endgenerate
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
from collections import OrderedDict
|
|
||||||
import timfuz
|
|
||||||
|
|
||||||
corner_s2i = OrderedDict(
|
|
||||||
[
|
|
||||||
('fast_max', 0),
|
|
||||||
('fast_min', 1),
|
|
||||||
('slow_max', 2),
|
|
||||||
('slow_min', 3),
|
|
||||||
])
|
|
||||||
|
|
||||||
corner2minmax = {
|
|
||||||
'fast_max': max,
|
|
||||||
'fast_min': min,
|
|
||||||
'slow_max': max,
|
|
||||||
'slow_min': min,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def merge_bdict(vi, vo):
|
|
||||||
'''
|
|
||||||
vi: input dictionary
|
|
||||||
vo: output dictionary
|
|
||||||
values are corner delay 4 tuples
|
|
||||||
'''
|
|
||||||
|
|
||||||
for name, bis in vi.items():
|
|
||||||
bos = vo.get(name, [None, None, None, None])
|
|
||||||
for cornerk, corneri in corner_s2i.items():
|
|
||||||
bo = bos[corneri]
|
|
||||||
bi = bis[corneri]
|
|
||||||
# no new data
|
|
||||||
if bi is None:
|
|
||||||
pass
|
|
||||||
# no previous data
|
|
||||||
elif bo is None:
|
|
||||||
bos[corneri] = bi
|
|
||||||
# combine
|
|
||||||
else:
|
|
||||||
minmax = corner2minmax[cornerk]
|
|
||||||
bos[corneri] = minmax(bi, bo)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_tiles(tileji, tilejo):
|
|
||||||
'''
|
|
||||||
{
|
|
||||||
"tiles": {
|
|
||||||
"BRKH_B_TERM_INT": {
|
|
||||||
"pips": {},
|
|
||||||
"wires": {
|
|
||||||
"B_TERM_UTURN_INT_ER1BEG0": [
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
93,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
'''
|
|
||||||
|
|
||||||
for tilek, tilevi in tileji.items():
|
|
||||||
# No previous data? Copy
|
|
||||||
tilevo = tilejo.get(tilek, None)
|
|
||||||
if tilevo is None:
|
|
||||||
tilejo[tilek] = tilevi
|
|
||||||
# Otherwise combine
|
|
||||||
else:
|
|
||||||
merge_bdict(tilevi['pips'], tilevo['pips'])
|
|
||||||
merge_bdict(tilevi['wires'], tilevo['wires'])
|
|
||||||
|
|
||||||
|
|
||||||
def merge_sites(siteji, sitejo):
|
|
||||||
for k, vi in siteji.items():
|
|
||||||
vo = sitejo.get(k, None)
|
|
||||||
# No previous data? Copy
|
|
||||||
if vo is None:
|
|
||||||
sitejo[k] = vi
|
|
||||||
# Otherwise combine
|
|
||||||
else:
|
|
||||||
merge_bdict(vi, vo)
|
|
||||||
|
|
||||||
|
|
||||||
def build_tilejo(fnins):
|
|
||||||
# merge all inputs into blank output
|
|
||||||
tilejo = {"tiles": {}, "sites": {}}
|
|
||||||
for fnin in fnins:
|
|
||||||
tileji = json.load(open(fnin, 'r'))
|
|
||||||
|
|
||||||
merge_tiles(tileji['tiles'], tilejo['tiles'])
|
|
||||||
merge_sites(tileji['sites'], tilejo['sites'])
|
|
||||||
|
|
||||||
return tilejo
|
|
||||||
|
|
||||||
|
|
||||||
def check_corner_minmax(tilej, verbose=False):
|
|
||||||
# Post processing pass looking for min/max inconsistencies
|
|
||||||
# Especially an issue due to complexities around under-constrained elements
|
|
||||||
# (ex: pivots set to 0 delay)
|
|
||||||
print('Checking for min/max consistency')
|
|
||||||
checks = 0
|
|
||||||
bad = 0
|
|
||||||
|
|
||||||
for tilev in tilej['tiles'].values():
|
|
||||||
|
|
||||||
def process_type(etype):
|
|
||||||
nonlocal checks
|
|
||||||
nonlocal bad
|
|
||||||
|
|
||||||
for pipk, pipv in tilev[etype].items():
|
|
||||||
for corner in ('slow', 'fast'):
|
|
||||||
mini = corner_s2i[corner + '_min']
|
|
||||||
minv = pipv[mini]
|
|
||||||
maxi = corner_s2i[corner + '_max']
|
|
||||||
maxv = pipv[maxi]
|
|
||||||
if minv is not None and maxv is not None:
|
|
||||||
checks += 1
|
|
||||||
if minv > maxv:
|
|
||||||
if verbose:
|
|
||||||
print(
|
|
||||||
'WARNING: element %s %s min/max adjusted on corner %s'
|
|
||||||
% (etype, pipk, corner))
|
|
||||||
bad += 1
|
|
||||||
pipv[mini] = maxv
|
|
||||||
pipv[maxi] = minv
|
|
||||||
|
|
||||||
process_type('pips')
|
|
||||||
process_type('wires')
|
|
||||||
|
|
||||||
print('')
|
|
||||||
print('minmax: %u / %u pairs bad pairs adjusted' % (bad, checks))
|
|
||||||
timfuz.tilej_stats(tilej)
|
|
||||||
|
|
||||||
|
|
||||||
def run(fnins, fnout, verbose=False):
|
|
||||||
tilejo = build_tilejo(fnins)
|
|
||||||
check_corner_minmax(tilejo)
|
|
||||||
# XXX: check fast vs slow?
|
|
||||||
# check_corners_minmax(tilejo)
|
|
||||||
json.dump(
|
|
||||||
tilejo,
|
|
||||||
open(fnout, 'w'),
|
|
||||||
sort_keys=True,
|
|
||||||
indent=4,
|
|
||||||
separators=(',', ': '))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Combine multiple tile corners into one .json file')
|
|
||||||
parser.add_argument(
|
|
||||||
'--out', required=True, help='Combined timgrid-v.json files')
|
|
||||||
parser.add_argument('fnins', nargs='+', help='Input timgrid-vc.json files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
run(args.fnins, args.out, verbose=False)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, A_di2ds, PREFIX_W, PREFIX_P
|
|
||||||
from timing_txt2json import gen_timing4n, load_speed_json
|
|
||||||
|
|
||||||
import glob
|
|
||||||
import math
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
# Verify the nodes and wires really do line up
|
|
||||||
def vals2Adi_check(vals, names):
|
|
||||||
print('Checking')
|
|
||||||
for val in vals:
|
|
||||||
node_wires = 0
|
|
||||||
for _node, wiresn in val['nodes']:
|
|
||||||
node_wires += wiresn
|
|
||||||
assert node_wires == len(val['wires'])
|
|
||||||
print('Done')
|
|
||||||
assert 0
|
|
||||||
|
|
||||||
|
|
||||||
def row_json2Ads(val, verbose=False):
|
|
||||||
'''Convert timing4 JSON into Ads interconnect equations'''
|
|
||||||
|
|
||||||
def pip2speed(pip):
|
|
||||||
_site, _name, pip_name = pip
|
|
||||||
return PREFIX_P + ':' + pip_name
|
|
||||||
|
|
||||||
def wire2speed(wire):
|
|
||||||
_site, _name, wire_name = wire
|
|
||||||
return PREFIX_W + ':' + wire_name
|
|
||||||
|
|
||||||
def add_name(name):
|
|
||||||
row_ds[name] = row_ds.get(name, 0) + 1
|
|
||||||
|
|
||||||
row_ds = {}
|
|
||||||
|
|
||||||
for pip in val['pips']:
|
|
||||||
add_name(pip2speed(pip))
|
|
||||||
for wire in val['wires']:
|
|
||||||
add_name(wire2speed(wire))
|
|
||||||
return row_ds
|
|
||||||
|
|
||||||
|
|
||||||
def load_Ads_gen(speed_json_f, fn_ins):
|
|
||||||
|
|
||||||
print('Loading data')
|
|
||||||
|
|
||||||
_speedj, speed_i2s = load_speed_json(speed_json_f)
|
|
||||||
|
|
||||||
for fn_in in fn_ins:
|
|
||||||
|
|
||||||
def mkb(val):
|
|
||||||
t = val['t']
|
|
||||||
return (t['fast_max'], t['fast_min'], t['slow_max'], t['slow_min'])
|
|
||||||
|
|
||||||
for val in gen_timing4n(fn_in, speed_i2s):
|
|
||||||
row_ds = row_json2Ads(val)
|
|
||||||
row_bs = mkb(val)
|
|
||||||
row_ico = val['ico']
|
|
||||||
|
|
||||||
yield row_bs, row_ds, row_ico
|
|
||||||
|
|
||||||
|
|
||||||
def run(speed_json_f, fout, fns_in, verbose=0, corner=None):
|
|
||||||
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
|
|
||||||
for row_bs, row_ds, row_ico in load_Ads_gen(speed_json_f, fns_in):
|
|
||||||
# XXX: consider removing ico column
|
|
||||||
# its obsolete at this point
|
|
||||||
if not row_ico:
|
|
||||||
continue
|
|
||||||
# like: 123 456 120 450, 1 a, 2 b
|
|
||||||
# first column has delay corners, followed by delay element count
|
|
||||||
items = [str(row_ico), ' '.join([str(x) for x in row_bs])]
|
|
||||||
for k, v in sorted(row_ds.items()):
|
|
||||||
items.append('%u %s' % (v, k))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Convert obscure timing4.txt into timing4i.csv (interconnect delay variable occurances)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
# made a bulk conversion easier...keep?
|
|
||||||
parser.add_argument(
|
|
||||||
'--auto-name', action='store_true', help='timing4.txt => timing4i.csv')
|
|
||||||
parser.add_argument(
|
|
||||||
'--speed-json',
|
|
||||||
default='build_speed/speed.json',
|
|
||||||
help='Provides speed index to name translation')
|
|
||||||
parser.add_argument('--out', default=None, help='Output timing4i.csv file')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='Input timing4.txt files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fnout = args.out
|
|
||||||
if fnout is None:
|
|
||||||
if args.auto_name:
|
|
||||||
assert len(args.fns_in) == 1
|
|
||||||
fnin = args.fns_in[0]
|
|
||||||
fnout = fnin.replace('.txt', 'i.csv')
|
|
||||||
assert fnout != fnin, 'Expect .txt in'
|
|
||||||
else:
|
|
||||||
# practically there are too many stray prints to make this work as expected
|
|
||||||
assert 0, 'File name required'
|
|
||||||
fnout = '/dev/stdout'
|
|
||||||
print("Writing to %s" % fnout)
|
|
||||||
fout = open(fnout, 'w')
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4.txt')
|
|
||||||
|
|
||||||
run(
|
|
||||||
speed_json_f=open(args.speed_json, 'r'),
|
|
||||||
fout=fout,
|
|
||||||
fns_in=fns_in,
|
|
||||||
verbose=args.verbose)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, A_di2ds
|
|
||||||
import glob
|
|
||||||
import math
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
# Speed index: some sort of special value
|
|
||||||
SI_NONE = 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
def parse_pip(s, speed_i2s):
|
|
||||||
# Entries like
|
|
||||||
# CLK_BUFG_REBUF_X60Y117/CLK_BUFG_REBUF.CLK_BUFG_REBUF_R_CK_GCLK0_BOT<<->>CLK_BUFG_REBUF_R_CK_GCLK0_TOP
|
|
||||||
# Convert to (site, type, pip_junction, pip)
|
|
||||||
pipstr, speed_index = s.split(':')
|
|
||||||
speed_index = int(speed_index)
|
|
||||||
site, instance = pipstr.split('/')
|
|
||||||
#type, pip_junction, pip = others.split('.')
|
|
||||||
#return (site, type, pip_junction, pip)
|
|
||||||
return site, instance, speed_i2s[int(speed_index)]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_pips(pips, speed_i2s):
|
|
||||||
if not pips:
|
|
||||||
return []
|
|
||||||
return [parse_pip(pip, speed_i2s) for pip in pips.split('|')]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_wire(s, speed_i2s):
|
|
||||||
# CLBLM_R_X3Y80/CLBLM_M_D6:952
|
|
||||||
wirestr, speed_index = s.split(':')
|
|
||||||
site, instance = wirestr.split('/')
|
|
||||||
return site, instance, speed_i2s[int(speed_index)]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_wires(wires, speed_i2s):
|
|
||||||
if not wires:
|
|
||||||
return []
|
|
||||||
return [parse_wire(wire, speed_i2s) for wire in wires.split('|')]
|
|
||||||
|
|
||||||
|
|
||||||
def gen_timing4(fn, speed_i2s):
|
|
||||||
f = open(fn, 'r')
|
|
||||||
header_want = "linetype,net,src_site,src_site_type,src_site_pin,src_bel,src_bel_pin,dst_site,dst_site_type,dst_site_pin,dst_bel,dst_bel_pin,ico,fast_max,fast_min,slow_max,slow_min,pips,wires"
|
|
||||||
ncols = len(header_want.split(','))
|
|
||||||
|
|
||||||
# src_bel dst_bel ico fast_max fast_min slow_max slow_min pips
|
|
||||||
header_got = f.readline().strip()
|
|
||||||
if header_got != header_want:
|
|
||||||
raise Exception("Unexpected columns")
|
|
||||||
|
|
||||||
rets = 0
|
|
||||||
# XXX: there were malformed lines, but think they are fixed now?
|
|
||||||
bads = 0
|
|
||||||
net_lines = 0
|
|
||||||
for l in f:
|
|
||||||
|
|
||||||
def group_line():
|
|
||||||
ncols = len('lintype,ico,delays'.split(','))
|
|
||||||
assert len(parts) == ncols
|
|
||||||
_lintype, ico, delays = parts
|
|
||||||
return int(ico), int(delays)
|
|
||||||
|
|
||||||
def net_line():
|
|
||||||
assert len(parts) == ncols, "Expected %u parts, got %u" % (
|
|
||||||
ncols, len(parts))
|
|
||||||
_lintype, net, src_site, src_site_type, src_site_pin, src_bel, src_bel_pin, dst_site, dst_site_type, dst_site_pin, dst_bel, dst_bel_pin, ico, fast_max, fast_min, slow_max, slow_min, pips, wires = parts
|
|
||||||
|
|
||||||
def filt_passthru_lut(bel_pins):
|
|
||||||
'''
|
|
||||||
Ex: SLICE_X11Y110/A6LUT/A6 SLICE_X11Y110/AFF/D
|
|
||||||
'''
|
|
||||||
parts = bel_pins.split()
|
|
||||||
if len(parts) == 1:
|
|
||||||
return parts[0]
|
|
||||||
else:
|
|
||||||
assert len(parts) == 2
|
|
||||||
# the LUT shoudl always go first?
|
|
||||||
bel_pin_lut, bel_pin_dst = parts
|
|
||||||
assert '6LUT' in bel_pin_lut
|
|
||||||
return bel_pin_dst
|
|
||||||
|
|
||||||
return {
|
|
||||||
'net': net,
|
|
||||||
'src': {
|
|
||||||
'site': src_site,
|
|
||||||
'site_type': src_site_type,
|
|
||||||
'site_pin': src_site_pin,
|
|
||||||
'bel': src_bel,
|
|
||||||
'bel_pin': src_bel_pin,
|
|
||||||
},
|
|
||||||
'dst': {
|
|
||||||
'site': dst_site,
|
|
||||||
'site_type': dst_site_type,
|
|
||||||
'site_pin': dst_site_pin,
|
|
||||||
'bel': dst_bel,
|
|
||||||
'bel_pin': filt_passthru_lut(dst_bel_pin),
|
|
||||||
},
|
|
||||||
't': {
|
|
||||||
# ps
|
|
||||||
'fast_max': int(fast_max),
|
|
||||||
'fast_min': int(fast_min),
|
|
||||||
'slow_max': int(slow_max),
|
|
||||||
'slow_min': int(slow_min),
|
|
||||||
},
|
|
||||||
'ico': int(ico),
|
|
||||||
'pips': parse_pips(pips, speed_i2s),
|
|
||||||
'wires': parse_wires(wires, speed_i2s),
|
|
||||||
'line': l,
|
|
||||||
}
|
|
||||||
|
|
||||||
l = l.strip()
|
|
||||||
if not l:
|
|
||||||
continue
|
|
||||||
parts = l.split(',')
|
|
||||||
lintype = parts[0]
|
|
||||||
|
|
||||||
val = {
|
|
||||||
'NET': net_line,
|
|
||||||
'GROUP': group_line,
|
|
||||||
}[lintype]()
|
|
||||||
yield lintype, val
|
|
||||||
|
|
||||||
rets += 1
|
|
||||||
print(' load %s: %d bad, %d good lines' % (fn, bads, rets))
|
|
||||||
|
|
||||||
|
|
||||||
def gen_timing4n(fn, speed_i2s):
|
|
||||||
'''Only generate nets'''
|
|
||||||
for lintype, val in gen_timing4(fn, speed_i2s):
|
|
||||||
if lintype == 'NET':
|
|
||||||
yield val
|
|
||||||
|
|
||||||
|
|
||||||
def gen_timing4a(fn, speed_i2s):
|
|
||||||
'''
|
|
||||||
Like above, but aggregate ico + non-ico into single entries
|
|
||||||
Key these based on uniqueness of (src_bel, dst_bel)
|
|
||||||
|
|
||||||
ico 0 is followed by 1
|
|
||||||
They should probably even be in the same order
|
|
||||||
Maybe just assert that?
|
|
||||||
'''
|
|
||||||
entries = {}
|
|
||||||
timgen = gen_timing4(fn, speed_i2s)
|
|
||||||
rets = 0
|
|
||||||
while True:
|
|
||||||
|
|
||||||
def get_ico(exp_ico):
|
|
||||||
ret = []
|
|
||||||
try:
|
|
||||||
lintype, val = next(timgen)
|
|
||||||
except StopIteration:
|
|
||||||
return None
|
|
||||||
assert lintype == 'GROUP'
|
|
||||||
ico, delays = val
|
|
||||||
assert ico == exp_ico
|
|
||||||
for _ in range(delays):
|
|
||||||
lintype, val = next(timgen)
|
|
||||||
assert lintype == 'NET'
|
|
||||||
ret.append(val)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
ico0s = get_ico(0)
|
|
||||||
if ico0s is None:
|
|
||||||
break
|
|
||||||
ico1s = get_ico(1)
|
|
||||||
# TODO: verify this is actually true
|
|
||||||
assert len(ico0s) == len(ico1s)
|
|
||||||
|
|
||||||
def same_path(l, r):
|
|
||||||
# if source and dest are the same, should be the same thing
|
|
||||||
return l['src']['bel_pin'] == r['src']['bel_pin'] and l['dst'][
|
|
||||||
'bel_pin'] == r['dst']['bel_pin']
|
|
||||||
|
|
||||||
for ico0, ico1 in zip(ico0s, ico1s):
|
|
||||||
# TODO: verify this is actually true
|
|
||||||
# otherwise move to more complex algorithm
|
|
||||||
assert same_path(ico0, ico1)
|
|
||||||
# aggregate timing info as (ic0, ic1) into ico0
|
|
||||||
ico0['t'] = (
|
|
||||||
ico0['t'],
|
|
||||||
ico1['t'],
|
|
||||||
)
|
|
||||||
yield ico0
|
|
||||||
rets += 1
|
|
||||||
print(' load %s: %u aggregated lines' % (fn, rets))
|
|
||||||
|
|
||||||
|
|
||||||
def load_speed_json(f):
|
|
||||||
j = json.load(f)
|
|
||||||
# Index speed indexes to names
|
|
||||||
speed_i2s = {}
|
|
||||||
for k, v in j['speed_model'].items():
|
|
||||||
i = v['speed_index']
|
|
||||||
if i != SI_NONE:
|
|
||||||
speed_i2s[i] = k
|
|
||||||
return j, speed_i2s
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from timfuz import Benchmark, A_di2ds, sw_ei_vals2s, sw_eo_vals2s, sw_i_vals2s
|
|
||||||
from timing_txt2json import gen_timing4a, load_speed_json
|
|
||||||
|
|
||||||
import glob
|
|
||||||
import math
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
def gen_diffs(speed_json_f, fns_in):
|
|
||||||
print('Loading data')
|
|
||||||
_speedj, speed_i2s = load_speed_json(speed_json_f)
|
|
||||||
|
|
||||||
for fn_in in fns_in:
|
|
||||||
for val in gen_timing4a(fn_in, speed_i2s):
|
|
||||||
# diff to get site only delay
|
|
||||||
tsites = {}
|
|
||||||
for k in val['t'][0].keys():
|
|
||||||
v = val['t'][0][k] - val['t'][1][k]
|
|
||||||
assert v >= 0
|
|
||||||
tsites[k] = v
|
|
||||||
yield val, tsites
|
|
||||||
|
|
||||||
|
|
||||||
# XXX: move to json converter?
|
|
||||||
def sd_parts(sd):
|
|
||||||
'''Return site_type, site_pin, bel_type, bel_pin as non-prefixed strings'''
|
|
||||||
# IOB_X0Y106 IOB_X0Y106/INBUF_EN IOB_X0Y106/INBUF_EN/OUT
|
|
||||||
# print(sd['site'], sd['bel'], sd['bel_pin'])
|
|
||||||
site_type = sd['site_type']
|
|
||||||
site, bel_type, bel_pin = sd['bel_pin'].split('/')
|
|
||||||
assert sd['site'] == site
|
|
||||||
assert sd['bel'] == site + '/' + bel_type
|
|
||||||
|
|
||||||
site_pin_str = sd['site_pin']
|
|
||||||
if site_pin_str:
|
|
||||||
site, site_pin = sd['site_pin'].split('/')
|
|
||||||
assert sd['site_pin'] == sd['site'] + '/' + site_pin
|
|
||||||
else:
|
|
||||||
site_pin = None
|
|
||||||
return site_type, site_pin, bel_type, bel_pin
|
|
||||||
|
|
||||||
|
|
||||||
def run(speed_json_f, fout, fns_in, verbose=0, corner=None):
|
|
||||||
'''
|
|
||||||
instead of writing to a simplified csv, lets just go directly to a delay format identical to what fabric uses
|
|
||||||
Path types:
|
|
||||||
-inter site: think these are removed for now?
|
|
||||||
1 model
|
|
||||||
NOTE: be careful of a net that goes external and comes back in, which isn't inter site
|
|
||||||
definition is that it doesn't have any site pins
|
|
||||||
-intra site
|
|
||||||
2 models
|
|
||||||
'''
|
|
||||||
|
|
||||||
fout.write(
|
|
||||||
'ico,fast_max fast_min slow_max slow_min,src_site_type,src_site,src_bel,src_bel_pin,dst_site_type,dst_site,dst_bel,dst_bel_pin\n'
|
|
||||||
)
|
|
||||||
for val, tsites in gen_diffs(speed_json_f, fns_in):
|
|
||||||
|
|
||||||
def mkb(t):
|
|
||||||
return (t['fast_max'], t['fast_min'], t['slow_max'], t['slow_min'])
|
|
||||||
|
|
||||||
bstr = ' '.join([str(x) for x in mkb(tsites)])
|
|
||||||
|
|
||||||
# Identify inter site transaction (SITEI)
|
|
||||||
if not val['src']['site_pin'] and not val['dst']['site_pin']:
|
|
||||||
# add one delay model for the path
|
|
||||||
# XXX: can these be solved exactly?
|
|
||||||
# might still have fanout and such
|
|
||||||
|
|
||||||
src_site_type, _src_site_pin, src_bel_type, src_bel_pin = sd_parts(
|
|
||||||
val['src'])
|
|
||||||
dst_site_type, _dst_site_pin, dst_bel_type, dst_bel_pin = sd_parts(
|
|
||||||
val['dst'])
|
|
||||||
assert src_site_type == dst_site_type
|
|
||||||
assert (src_bel_type, src_bel_pin) != (dst_bel_type, dst_bel_pin)
|
|
||||||
|
|
||||||
k = sw_i_vals2s(
|
|
||||||
src_site_type, src_bel_type, src_bel_pin, dst_bel_type,
|
|
||||||
dst_bel_pin)
|
|
||||||
row_ds = {k: 1}
|
|
||||||
elif val['src']['site_pin'] and val['dst']['site_pin']:
|
|
||||||
# if it exits a site it should enter another (possibly the same site)
|
|
||||||
# site in (SITEI) or site out (SITEO)?
|
|
||||||
# nah, keep things simple and just call them SITEW
|
|
||||||
row_ds = {}
|
|
||||||
|
|
||||||
def add_dst_delay():
|
|
||||||
sd = val['dst']
|
|
||||||
site_type, src_site_pin, dst_bel, dst_bel_pin = sd_parts(sd)
|
|
||||||
k = sw_ei_vals2s(site_type, src_site_pin, dst_bel, dst_bel_pin)
|
|
||||||
assert k not in row_ds
|
|
||||||
row_ds[k] = 1
|
|
||||||
|
|
||||||
def add_src_delay():
|
|
||||||
sd = val['src']
|
|
||||||
site_type, dst_site_pin, src_bel, src_bel_pin = sd_parts(sd)
|
|
||||||
k = sw_eo_vals2s(site_type, src_bel, src_bel_pin, dst_site_pin)
|
|
||||||
assert k not in row_ds
|
|
||||||
row_ds[k] = 1
|
|
||||||
|
|
||||||
add_dst_delay()
|
|
||||||
add_src_delay()
|
|
||||||
else:
|
|
||||||
# dropped by the tcl script
|
|
||||||
raise Exception("FIXME: handle destination but no source")
|
|
||||||
|
|
||||||
row_ico = 0
|
|
||||||
items = [str(row_ico), bstr]
|
|
||||||
for k, v in sorted(row_ds.items()):
|
|
||||||
items.append('%u %s' % (v, k))
|
|
||||||
fout.write(','.join(items) + '\n')
|
|
||||||
print('done')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description=
|
|
||||||
'Convert obscure timing4.txt into timing4s.csv (site delay variable occurances)'
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument('--verbose', type=int, help='')
|
|
||||||
# made a bulk conversion easier...keep?
|
|
||||||
parser.add_argument(
|
|
||||||
'--auto-name', action='store_true', help='timing4.txt => timing4i.csv')
|
|
||||||
parser.add_argument(
|
|
||||||
'--speed-json',
|
|
||||||
default='build_speed/speed.json',
|
|
||||||
help='Provides speed index to name translation')
|
|
||||||
parser.add_argument('--out', default=None, help='Output timing4i.csv file')
|
|
||||||
parser.add_argument('fns_in', nargs='+', help='Input timing4.txt files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
bench = Benchmark()
|
|
||||||
|
|
||||||
fnout = args.out
|
|
||||||
if fnout is None:
|
|
||||||
if args.auto_name:
|
|
||||||
assert len(args.fns_in) == 1
|
|
||||||
fnin = args.fns_in[0]
|
|
||||||
fnout = fnin.replace('.txt', 's.csv')
|
|
||||||
assert fnout != fnin, 'Expect .txt in'
|
|
||||||
else:
|
|
||||||
# practically there are too many stray prints to make this work as expected
|
|
||||||
assert 0, 'File name required'
|
|
||||||
fnout = '/dev/stdout'
|
|
||||||
print("Writing to %s" % fnout)
|
|
||||||
fout = open(fnout, 'w')
|
|
||||||
|
|
||||||
fns_in = args.fns_in
|
|
||||||
if not fns_in:
|
|
||||||
fns_in = glob.glob('specimen_*/timing4.txt')
|
|
||||||
|
|
||||||
run(
|
|
||||||
speed_json_f=open(args.speed_json, 'r'),
|
|
||||||
fout=fout,
|
|
||||||
fns_in=fns_in,
|
|
||||||
verbose=args.verbose)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Loading…
Reference in New Issue