Merge pull request #126 from mcmasterg/timfuz_site

timing site fuzzer
This commit is contained in:
John McMaster 2018-10-09 16:45:45 -07:00 committed by GitHub
commit 867853076d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 1284 additions and 597 deletions

View File

@ -1,7 +1,7 @@
# for now hammering on just picorv32 # for now hammering on just picorv32
# consider instead aggregating multiple projects # consider instead aggregating multiple projects
PRJ?=picorv32 PRJ?=oneblinkw
PRJN?=8 PRJN?=1
all: build/timgrid-v.json all: build/timgrid-v.json

View File

@ -60,7 +60,7 @@ def pds(Ads, s):
def run(fns_in, sub_json=None, verbose=False): def run(fns_in, sub_json=None, verbose=False):
assert len(fns_in) > 0 assert len(fns_in) > 0
# arbitrary corner...data is thrown away # arbitrary corner...data is thrown away
Ads, b = loadc_Ads_b(fns_in, "slow_max", ico=True) Ads, b = loadc_Ads_b(fns_in, "slow_max")
if sub_json: if sub_json:
print('Subbing JSON %u rows' % len(Ads)) print('Subbing JSON %u rows' % len(Ads))
@ -111,14 +111,14 @@ def main():
parser.add_argument('--verbose', action='store_true', help='') parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--sub-json', help='') parser.add_argument('--sub-json', help='')
parser.add_argument('fns_in', nargs='*', help='timing3.csv input files') parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
# Store options in dict to ease passing through functions # Store options in dict to ease passing through functions
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None sub_json = None
if args.sub_json: if args.sub_json:

View File

@ -28,10 +28,12 @@ def main():
parser.add_argument('--verbose', type=int, help='') parser.add_argument('--verbose', type=int, help='')
parser.add_argument( parser.add_argument(
'--auto-name', action='store_true', help='timing3.csv => timing3c.csv') '--auto-name',
action='store_true',
help='timing4i.csv => timing4c.csv')
parser.add_argument('--out', default=None, help='Output csv') parser.add_argument('--out', default=None, help='Output csv')
parser.add_argument('--corner', help='Output csv') parser.add_argument('--corner', help='Output csv')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files') parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
bench = Benchmark() bench = Benchmark()
@ -40,8 +42,8 @@ def main():
if args.auto_name: if args.auto_name:
assert len(args.fns_in) == 1 assert len(args.fns_in) == 1
fnin = args.fns_in[0] fnin = args.fns_in[0]
fnout = fnin.replace('timing3.csv', 'timing3c.csv') fnout = fnin.replace('timing4i.csv', 'timing4c.csv')
assert fnout != fnin, 'Expect timing3.csv in' assert fnout != fnin, 'Expect timing4i.csv in'
else: else:
fnout = '/dev/stdout' fnout = '/dev/stdout'
print("Writing to %s" % fnout) print("Writing to %s" % fnout)
@ -49,7 +51,7 @@ def main():
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
run(fout=fout, fns_in=fns_in, corner=args.corner, verbose=args.verbose) run(fout=fout, fns_in=fns_in, corner=args.corner, verbose=args.verbose)

View File

@ -5,7 +5,7 @@ from timfuz import Benchmark, loadc_Ads_bs, index_names, load_sub, run_sub_json,
def gen_group(fnin, sub_json, strict=False, verbose=False): def gen_group(fnin, sub_json, strict=False, verbose=False):
print('Loading data') print('Loading data')
Ads, bs = loadc_Ads_bs([fnin], ico=True) Ads, bs = loadc_Ads_bs([fnin])
print('Sub: %u rows' % len(Ads)) print('Sub: %u rows' % len(Ads))
iold = instances(Ads) iold = instances(Ads)
@ -45,14 +45,14 @@ def main():
required=True, required=True,
help='Group substitutions to make fully ranked') help='Group substitutions to make fully ranked')
parser.add_argument('--out', help='Output sub.json substitution result') parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files') parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
# Store options in dict to ease passing through functions # Store options in dict to ease passing through functions
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = load_sub(args.sub_json) sub_json = load_sub(args.sub_json)

View File

@ -4,7 +4,7 @@ from timfuz import Benchmark, loadc_Ads_bs, load_sub, Ads2bounds, corners2csv, c
def gen_flat(fns_in, sub_json, corner=None): def gen_flat(fns_in, sub_json, corner=None):
Ads, bs = loadc_Ads_bs(fns_in, ico=True) Ads, bs = loadc_Ads_bs(fns_in)
bounds = Ads2bounds(Ads, bs) bounds = Ads2bounds(Ads, bs)
# Elements with zero delay assigned due to sub group # Elements with zero delay assigned due to sub group
group_zeros = set() group_zeros = set()

View File

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

View File

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

View File

@ -4,7 +4,8 @@ TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
CORNER=slow_max CORNER=slow_max
ALLOW_ZERO_EQN?=N ALLOW_ZERO_EQN?=N
BADPRJ_OK?=N BADPRJ_OK?=N
BUILD_DIR?=build BUILD_DIR?=build/MUST_SET
CSV_BASENAME=timing4i.csv
all: $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/qor.txt all: $(BUILD_DIR)/$(CORNER)/timgrid-vc.json $(BUILD_DIR)/$(CORNER)/qor.txt
@ -19,7 +20,7 @@ clean:
.PHONY: all run clean .PHONY: all run clean
$(BUILD_DIR)/$(CORNER): $(BUILD_DIR)/$(CORNER):
mkdir $(BUILD_DIR)/$(CORNER) mkdir -p $(BUILD_DIR)/$(CORNER)
# parent should have built this # parent should have built this
$(BUILD_DIR)/checksub: $(BUILD_DIR)/checksub:
@ -27,12 +28,12 @@ $(BUILD_DIR)/checksub:
$(BUILD_DIR)/$(CORNER)/leastsq.csv: $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv $(BUILD_DIR)/checksub $(BUILD_DIR)/$(CORNER) $(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 # Create a rough timing model that approximately fits the given paths
python3 $(TIMFUZ_DIR)/solve_leastsq.py --sub-json $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/leastsq.csv.tmp 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 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 $(BUILD_DIR)/$(CORNER)/linprog.csv: $(BUILD_DIR)/$(CORNER)/leastsq.csv $(BUILD_DIR)/grouped.csv
# Tweak rough timing model, making sure all constraints are satisfied # Tweak rough timing model, making sure all constraints are satisfied
ALLOW_ZERO_EQN=$(ALLOW_ZERO_EQN) python3 $(TIMFUZ_DIR)/solve_linprog.py --sub-json $(BUILD_DIR)/sub.json --bounds-csv $(BUILD_DIR)/$(CORNER)/leastsq.csv --massage $(BUILD_DIR)/grouped.csv --corner $(CORNER) --out $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp 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 mv $(BUILD_DIR)/$(CORNER)/linprog.csv.tmp $(BUILD_DIR)/$(CORNER)/linprog.csv
$(BUILD_DIR)/$(CORNER)/flat.csv: $(BUILD_DIR)/$(CORNER)/linprog.csv $(BUILD_DIR)/$(CORNER)/flat.csv: $(BUILD_DIR)/$(CORNER)/linprog.csv
@ -46,6 +47,11 @@ $(BUILD_DIR)/$(CORNER)/timgrid-vc.json: $(BUILD_DIR)/$(CORNER)/flat.csv
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 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 $(BUILD_DIR)/$(CORNER)/qor.txt: $(BUILD_DIR)/$(CORNER)/flat.csv
python3 $(TIMFUZ_DIR)/solve_qor.py --corner $(CORNER) --bounds-csv $(BUILD_DIR)/$(CORNER)/flat.csv specimen_*/timing3.csv >$(BUILD_DIR)/$(CORNER)/qor.txt.tmp 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 mv $(BUILD_DIR)/$(CORNER)/qor.txt.tmp $(BUILD_DIR)/$(CORNER)/qor.txt
else
# FIXME
touch $(BUILD_DIR)/$(CORNER)/qor.txt
endif

View File

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

View File

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

View File

@ -43,5 +43,5 @@ proc build_design {} {
} }
build_design build_design
write_info3 write_info4

View File

@ -44,5 +44,5 @@ proc build_design {} {
} }
build_design build_design
write_info3 write_info4

View File

@ -7,5 +7,5 @@ TIMFUZ_DIR=$XRAY_DIR/fuzzers/007-timing
python ../generate.py --sdx 4 --sdy 4 >top.v python ../generate.py --sdx 4 --sdy 4 >top.v
vivado -mode batch -source ../generate.tcl vivado -mode batch -source ../generate.tcl
python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing3.csv timing3.txt python3 $TIMFUZ_DIR/timing_txt2csv.py --speed-json $TIMFUZ_DIR/speed/build/speed.json --out timing4.csv timing4.txt

View File

@ -43,5 +43,5 @@ proc build_design {} {
} }
build_design build_design
write_info3 write_info4

View File

@ -43,5 +43,5 @@ proc build_design {} {
} }
build_design build_design
write_info3 write_info4

View File

@ -43,5 +43,5 @@ proc build_design {} {
} }
build_design build_design
write_info3 write_info4

View File

@ -4,38 +4,26 @@
N := 1 N := 1
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N))) SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS)) SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
CSVS := $(addsuffix /timing3.csv,$(SPECIMENS))
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
RREF_CORNER=slow_max
# Allow an empty system of equations? # Allow an empty system of equations?
# for testing only on small projects # for testing only on small projects
ALLOW_ZERO_EQN?=N ALLOW_ZERO_EQN?=N
# Constrained projects may fail to build # Constrained projects may fail to build
# Set to Y to make a best effort to suck in the ones that did build # Set to Y to make a best effort to suck in the ones that did build
BADPRJ_OK?=N BADPRJ_OK?=N
# Set ZERO elements to zero delay (as is expected they should be)
RMZERO?=N
BUILD_DIR?=build BUILD_DIR?=build
# interconnect
BUILD_DIR_I?=$(BUILD_DIR)/i
# site
BUILD_DIR_S?=$(BUILD_DIR)/s
RREF_ARGS= CORNERS=fast_max fast_min slow_max slow_min
ifeq ($(RMZERO),Y)
RREF_ARGS+=--rm-zero
endif
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_V_I=$(BUILD_DIR_I)/timgrid-v.json
TIMGRID_V_S=$(BUILD_DIR_S)/timgrid-v.json
all: $(BUILD_DIR)/timgrid-v.json all: $(BUILD_DIR)/timgrid-v.json
# 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
$(SPECIMENS_OK): $(SPECIMENS_OK):
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0) bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
touch $@ touch $@
@ -52,38 +40,16 @@ clean:
.PHONY: all run clean .PHONY: all run clean
# Normally require all projects to complete $(TIMGRID_V_I): $(SPECIMENS_OK)
# If BADPRJ_OK is allowed, only take projects that were successful $(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_I) SOLVING=i SPECIMENS=$(SPECIMENS) all
# FIXME: couldn't get call to work i: $(TIMGRID_V_I)
exist_csvs = \
for f in $(CSVS); do \
if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then \
echo $$f; \
fi; \
done
# rref should be the same regardless of corner $(TIMGRID_V_S): $(SPECIMENS_OK)
$(BUILD_DIR)/sub.json: $(SPECIMENS_OK) $(MAKE) -f $(TIMFUZ_DIR)/projects/is.mk BUILD_DIR=$(BUILD_DIR_S) SOLVING=s SPECIMENS=$(SPECIMENS) all
mkdir -p $(BUILD_DIR) s: $(TIMGRID_V_S)
# Discover which variables can be separated
# This is typically the longest running operation
\
csvs=$$(for f in $(CSVS); do if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then echo $$f; fi; done) ; \
python3 $(TIMFUZ_DIR)/rref.py --corner $(RREF_CORNER) --simplify $(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 .PHONY: i s
# 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 $(BUILD_DIR)/timgrid-v.json: $(TIMGRID_V_I) $(TIMGRID_V_S)
# Verify sub.json makes a cleanly solvable solution with no non-pivot leftover python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_V_I) $(TIMGRID_V_S)
python3 $(TIMFUZ_DIR)/checksub.py --sub-json $(BUILD_DIR)/sub.json $(BUILD_DIR)/grouped.csv
touch $(BUILD_DIR)/checksub
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_VCS)
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_VCS)

View File

@ -1,166 +1,205 @@
proc pin_info {pin} { # General notes:
set cell [get_cells -of_objects $pin] # Observed nets with 1 to 2 source cell pins
set bel [get_bels -of_objects $cell] # 2 example
set site [get_sites -of_objects $bel] # [get_pins -filter {DIRECTION == OUT} -of_objects $net]
return "$site $bel" # 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 pin_bel {pin} { 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} {
set cell [get_cells -of_objects $pin] 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"
set bel [get_bels -of_objects $cell]
return $bel
} }
# Changed to group wires and nodes 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} {
# This allows tracing the full path along with pips set src_site $site
proc write_info3 {} { 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 outdir "."
set fp [open "$outdir/timing3.txt" w] set fp [open "$outdir/timing4.txt" w]
# bel as site/bel, so don't bother with site # bel as site/bel, so don't bother with site
puts $fp "net src_bel dst_bel ico fast_max fast_min slow_max slow_min pips inodes wires" 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 TIME_start [clock clicks -milliseconds]
set verbose 0 set delays_net 0
set equations 0 set nets_no_src_cell 0
set site_src_nets 0 set nets_no_src_bel 0
set site_dst_nets 0 set lines_no_int 0
set lines_some_int 0
set neti 0 set neti 0
set nets [get_nets -hierarchical] 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] set nnets [llength $nets]
foreach net $nets { foreach net $nets {
incr neti incr neti
#if {$neti >= 10} {
# puts "Debug break"
# break
#}
puts "Net $neti / $nnets: $net" 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 # The semantics of get_pins -leaf is kind of odd
# When no passthrough LUTs exist, it has no effect # When no passthrough LUTs exist, it has no effect
# When passthrough LUT present: # When passthrough LUT present:
# -w/o -leaf: some pins + passthrough LUT pins # -w/o -leaf: some pins + passthrough LUT pins
# -w/ -leaf: different pins + passthrough LUT pins # -w/ -leaf: different pins + passthrough LUT pins
# With OUT filter this seems to be sufficient # With OUT filter this seems to be sufficient
set src_pin [get_pins -leaf -filter {DIRECTION == OUT} -of_objects $net] set src_cell_pins [get_pins -leaf -filter {DIRECTION == OUT} -of_objects $net]
set src_bel [pin_bel $src_pin] 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 [get_sites -of_objects $src_bel]
# Only one net driver set src_site_type [get_property SITE_TYPE $src_site]
set src_site_pins [get_site_pins -filter {DIRECTION == OUT} -of_objects $net] # optional
set src_site_pin [get_site_pins -of_objects $src_cell_pin]
# Sometimes this is empty for reasons that escape me # Report delays with and without interconnect only
# Emitting direction doesn't help foreach ico "0 1" {
if {[llength $src_site_pins] < 1} { if $ico {
if $verbose { set delays [get_net_delays -interconnect_only -of_objects $net]
puts " Ignoring site internal net" } else {
} set delays [get_net_delays -of_objects $net]
incr site_src_nets
continue
}
set dst_site_pins_net [get_site_pins -filter {DIRECTION == IN} -of_objects $net]
if {[llength $dst_site_pins_net] < 1} {
puts " Skipping site internal source net"
incr site_dst_nets
continue
}
foreach src_site_pin $src_site_pins {
if $verbose {
puts "Source: $src_pin at site $src_site:$src_bel, spin $src_site_pin"
} }
set delays_net [expr "$delays_net + [llength $delays]"]
puts $fp "GROUP,$ico,[llength $delays]"
foreach delay $delays {
#set delaystr [get_property NAME $delay]
# Run with and without interconnect only set fast_max [get_property "FAST_MAX" $delay]
foreach ico "0 1" { set fast_min [get_property "FAST_MIN" $delay]
set ico_flag "" set slow_max [get_property "SLOW_MAX" $delay]
if $ico { set slow_min [get_property "SLOW_MIN" $delay]
set ico_flag "-interconnect_only"
set delays [get_net_delays $ico_flag -of_objects $net] # 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 { } else {
set delays [get_net_delays -of_objects $net] # Nested list delimination precedence: ",|:"
}
foreach delay $delays { # Pips in between
set delaystr [get_property NAME $delay] # Walk net, looking for interesting elements in between
set dst_pins [get_property TO_PIN $delay] # -from <args> - (Optional) Defines the starting points of the pips to get
set dst_pin [get_pins $dst_pins] # from wire or site_pin
#puts " $delaystr: $src_pin => $dst_pin" set pips [get_pips -of_objects $net -from $src_site_pin -to $dst_site_pin]
set dst_bel [pin_bel $dst_pin] # Write pips w/ speed index
set dst_site [get_sites -of_objects $dst_bel] foreach pip $pips {
if $verbose { set speed_index [get_property SPEED_INDEX $pip]
puts " Dest: $dst_pin at site $dst_site:$dst_bel" 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 dst_site_pins [get_site_pins -of_objects $dst_pin] set wires [get_wires -of_objects $nodes]
# Some nets are internal # Write wires
# But should this happen on dest if we've already filtered source? foreach wire $wires {
if {"$dst_site_pins" eq ""} { set speed_index [get_property SPEED_INDEX $wire]
continue lappend wires_out "$wire:$speed_index"
} }
# Also apparantly you can have multiple of these as well set wires_out [list_format "$wires_out" "|"]
foreach dst_site_pin $dst_site_pins {
set fast_max [get_property "FAST_MAX" $delay]
set fast_min [get_property "FAST_MIN" $delay]
set slow_max [get_property "SLOW_MAX" $delay]
set slow_min [get_property "SLOW_MIN" $delay]
# Want: incr lines_some_int
# Site / BEL at src 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
# Site / BEL at dst
# Pips in between
# Walk net, looking for interesting elements in between
set pips [get_pips -of_objects $net -from $src_site_pin -to $dst_site_pin]
if $verbose {
foreach pip $pips {
puts " PIP $pip"
}
}
set nodes [get_nodes -of_objects $net -from $src_site_pin -to $dst_site_pin]
#set wires [get_wires -of_objects $net -from $src_site_pin -to $dst_site_pin]
set wires [get_wires -of_objects $nodes]
# puts $fp "$net $src_bel $dst_bel $ico $fast_max $fast_min $slow_max $slow_min $pips"
puts -nonewline $fp "$net $src_bel $dst_bel $ico $fast_max $fast_min $slow_max $slow_min"
# Write pips w/ speed index
puts -nonewline $fp " "
set needspace 0
foreach pip $pips {
if $needspace {
puts -nonewline $fp "|"
}
set speed_index [get_property SPEED_INDEX $pip]
puts -nonewline $fp "$pip:$speed_index"
set needspace 1
}
# Write nodes
#set nodes_str [string map {" " "|"} $nodes]
#puts -nonewline $fp " $nodes_str"
puts -nonewline $fp " "
set needspace 0
foreach node $nodes {
if $needspace {
puts -nonewline $fp "|"
}
set nwires [llength [get_wires -of_objects $node]]
puts -nonewline $fp "$node:$nwires"
set needspace 1
}
# Write wires
puts -nonewline $fp " "
set needspace 0
foreach wire $wires {
if $needspace {
puts -nonewline $fp "|"
}
set speed_index [get_property SPEED_INDEX $wire]
puts -nonewline $fp "$wire:$speed_index"
set needspace 1
}
puts $fp ""
incr equations
break
}
} }
} }
} }
@ -168,7 +207,16 @@ proc write_info3 {} {
close $fp close $fp
set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start] set TIME_taken [expr [clock clicks -milliseconds] - $TIME_start]
puts "Took ms: $TIME_taken" puts "Took ms: $TIME_taken"
puts "Generated $equations equations" puts "Checked $delays_net delays"
puts "Skipped $site_src_nets (+ $site_dst_nets) site nets" puts "Nets: $nnets"
puts " Skipped (no source cell): $nets_no_src_cell"
puts " Skipped (no source bel): $nets_no_src_bel"
puts "Lines"
puts " No interconnect: $lines_no_int"
puts " Has interconnect: $lines_some_int"
} }
# for debugging
# source ../project.tcl
# write_info4

View File

@ -73,7 +73,7 @@ class State(object):
def load(fn_ins, simplify=False, corner=None, rm_zero=False): def load(fn_ins, simplify=False, corner=None, rm_zero=False):
zero_names = OrderedSet() zero_names = OrderedSet()
Ads, b = loadc_Ads_b(fn_ins, corner=corner, ico=True) Ads, b = loadc_Ads_b(fn_ins, corner=corner)
if rm_zero: if rm_zero:
zero_names = rm_zero_cols(Ads) zero_names = rm_zero_cols(Ads)
if simplify: if simplify:
@ -223,13 +223,13 @@ def main():
default='build_speed/speed.json', default='build_speed/speed.json',
help='Provides speed index to name translation') help='Provides speed index to name translation')
parser.add_argument('--out', help='Output sub.json substitution result') parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='*', help='timing3.csv input files') parser.add_argument('fns_in', nargs='*', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
try: try:
run( run(

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from timfuz import Benchmark, load_sub, corner_s2i, acorner2csv from timfuz import Benchmark, load_sub
import timfuz import timfuz
import numpy as np import numpy as np
import math import math
@ -22,11 +22,20 @@ def mkestimate(Anp, b):
x0 = np.array([1e3 for _x in range(cols)]) x0 = np.array([1e3 for _x in range(cols)])
for row_np, row_b in zip(Anp, b): for row_np, row_b in zip(Anp, b):
for coli, val in enumerate(row_np): for coli, val in enumerate(row_np):
if val: # favor non-trivial values
# Scale by number occurances if val <= 0:
ub = row_b / val continue
if ub >= 0: # Scale by number occurances
x0[coli] = min(x0[coli], ub) 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 return x0
@ -119,14 +128,14 @@ def main():
parser.add_argument('--corner', required=True, default="slow_max", help='') parser.add_argument('--corner', required=True, default="slow_max", help='')
parser.add_argument( parser.add_argument(
'--out', default=None, help='output timing delay .json') '--out', default=None, help='output timing delay .json')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files') parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
# Store options in dict to ease passing through functions # Store options in dict to ease passing through functions
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None sub_json = None
if args.sub_json: if args.sub_json:

View File

@ -162,14 +162,14 @@ def main():
parser.add_argument('--corner', required=True, default="slow_max", help='') parser.add_argument('--corner', required=True, default="slow_max", help='')
parser.add_argument( parser.add_argument(
'--out', default=None, help='output timing delay .json') '--out', default=None, help='output timing delay .json')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files') parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
# Store options in dict to ease passing through functions # Store options in dict to ease passing through functions
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None sub_json = None
if args.sub_json: if args.sub_json:

View File

@ -6,7 +6,7 @@ import numpy as np
def run(fns_in, corner, bounds_csv, verbose=False): def run(fns_in, corner, bounds_csv, verbose=False):
print('Loading data') print('Loading data')
Ads, borig = loadc_Ads_b(fns_in, corner, ico=True) Ads, borig = loadc_Ads_b(fns_in, corner)
bounds = load_bounds(bounds_csv, corner) bounds = load_bounds(bounds_csv, corner)
# verify is flattened # verify is flattened
@ -37,14 +37,14 @@ def main():
'--bounds-csv', '--bounds-csv',
required=True, required=True,
help='Previous solve result starting point') help='Previous solve result starting point')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files') parser.add_argument('fns_in', nargs='+', help='timing4i.csv input files')
args = parser.parse_args() args = parser.parse_args()
# Store options in dict to ease passing through functions # Store options in dict to ease passing through functions
bench = Benchmark() bench = Benchmark()
fns_in = args.fns_in fns_in = args.fns_in
if not fns_in: if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv') fns_in = glob.glob('specimen_*/timing4i.csv')
try: try:
run( run(

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import timfuz import timfuz
from timfuz import loadc_Ads_bs, Ads2bounds 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 sys
import os import os
@ -9,22 +9,117 @@ import time
import json 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): def run(fns_in, fnout, tile_json_fn, verbose=False):
# modified in place # modified in place
tilej = json.load(open(tile_json_fn, 'r')) tilej = json.load(open(tile_json_fn, 'r'))
for fnin in fns_in: for fnin in fns_in:
Ads, bs = loadc_Ads_bs([fnin], ico=True) Ads, bs = loadc_Ads_bs([fnin])
bounds = Ads2bounds(Ads, bs) bounds = Ads2bounds(Ads, bs)
bounds_pw, bounds_sites = sep_bounds(bounds)
print(len(bounds), len(bounds_pw), len(bounds_sites))
for tile in tilej['tiles'].values(): add_pip_wire(tilej, bounds_pw)
pips = tile['pips'] add_sites(tilej, bounds_sites)
for k, v in pips.items():
pips[k] = bounds.get('PIP_' + v, [None, None, None, None])
wires = tile['wires']
for k, v in wires.items():
wires[k] = bounds.get('WIRE_' + v, [None, None, None, None])
timfuz.tilej_stats(tilej) timfuz.tilej_stats(tilej)

View File

@ -17,6 +17,51 @@ import collections
from benchmark import Benchmark 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 # Equations are filtered out until nothing is left
class SimplifiedToZero(Exception): class SimplifiedToZero(Exception):
@ -524,11 +569,14 @@ def loadc_Ads_mkb(fns, mkb, filt):
def loadc_Ads_b(fns, corner, ico=None): def loadc_Ads_b(fns, corner, ico=None):
corner = corner or "slow_max" corner = corner or "slow_max"
corneri = corner_s2i[corner] corneri = corner_s2i[corner]
'''
if ico is not None: if ico is not None:
filt = lambda ico_, corners, vars: ico_ == ico filt = lambda ico_, corners, vars: ico_ == ico
else: else:
filt = lambda ico_, corners, vars: True 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): def mkb(val):
return val[corneri] return val[corneri]
@ -537,10 +585,14 @@ def loadc_Ads_b(fns, corner, ico=None):
def loadc_Ads_bs(fns, ico=None): def loadc_Ads_bs(fns, ico=None):
'''
if ico is not None: if ico is not None:
filt = lambda ico_, corners, vars: ico_ == ico filt = lambda ico_, corners, vars: ico_ == ico
else: else:
filt = lambda ico_, corners, vars: True 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): def mkb(val):
return val return val
@ -730,35 +782,63 @@ def corners2csv(bs):
def tilej_stats(tilej): def tilej_stats(tilej):
stats = {} def tile_stats():
for etype in ('pips', 'wires'): stats = {}
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'): for etype in ('pips', 'wires'):
pips = tile[etype] tm = stats.setdefault(etype, {})
for k, v in pips.items(): tm['net'] = 0
stats[etype]['net'] += 1 tm['solved'] = [0, 0, 0, 0]
for i in range(4): tm['covered'] = [0, 0, 0, 0]
if pips[k][i]:
stats[etype]['solved'][i] += 1 for tile in tilej['tiles'].values():
if pips[k][i] is not None: for etype in ('pips', 'wires'):
stats[etype]['covered'][i] += 1 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(): for corner, corneri in corner_s2i.items():
print('Corner %s' % corner) print('Corner %s' % corner)
for etype in ('pips', 'wires'): for etype in ('pips', 'wires'):
net = stats[etype]['net'] net = tstats[etype]['net']
solved = stats[etype]['solved'][corneri] solved = tstats[etype]['solved'][corneri]
covered = stats[etype]['covered'][corneri] covered = tstats[etype]['covered'][corneri]
print( print(
' %s: %u / %u solved, %u / %u covered' % ' %s: %u / %u solved, %u / %u covered' %
(etype, solved, net, covered, net)) (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, ico=True): def load_bounds(bounds_csv, corner):
Ads, b = loadc_Ads_b([bounds_csv], corner, ico=ico) Ads, b = loadc_Ads_b([bounds_csv], corner)
return Ads2bounds(Ads, b) return Ads2bounds(Ads, b)

View File

@ -220,7 +220,9 @@ def derive_eq_by_col(Ads, b_ub, verbose=0, keep_orig=True):
# iteratively increasing column limit until all columns are added # iteratively increasing column limit until all columns are added
def massage_equations(Ads, b, verbose=False, corner=None): 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 Subtract equations from each other to generate additional constraints
Helps provide additional guidance to solver for realistic delays Helps provide additional guidance to solver for realistic delays
@ -262,7 +264,6 @@ def massage_equations(Ads, b, verbose=False, corner=None):
# Each iteration one more column is allowed until all columns are included # Each iteration one more column is allowed until all columns are included
# (and the system is stable) # (and the system is stable)
col_lim = 15
di = 0 di = 0
while True: while True:
print print
@ -298,13 +299,13 @@ def massage_equations(Ads, b, verbose=False, corner=None):
col_dist(Ads, 'derive done iter %d, lim %d' % (di, col_lim), lim=12) col_dist(Ads, 'derive done iter %d, lim %d' % (di, col_lim), lim=12)
rows = len(Ads) rows = len(Ads)
di += 1
dend = len(b)
# possible that a new equation was generated and taken away, but close enough # possible that a new equation was generated and taken away, but close enough
if n_orig == len(b) and col_lim >= cols: if n_orig == len(b) and col_lim >= cols or di >= iter_lim:
break break
col_lim += col_lim / 5 col_lim += col_lim / 5
di += 1
dend = len(b)
print('') print('')
print('Derive net: %d => %d' % (dstart, dend)) print('Derive net: %d => %d' % (dstart, dend))
print('') print('')

View File

@ -150,7 +150,10 @@ def solve_save(outfn, xvals, names, corner, save_zero=True, verbose=False):
print( print(
'Wrote: %u / %u constrained delays, %u zeros' % 'Wrote: %u / %u constrained delays, %u zeros' %
(nonzeros, len(names), zeros)) (nonzeros, len(names), zeros))
assert nonzeros, 'Failed to estimate delay' # 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( def run(
@ -165,7 +168,7 @@ def run(
verbose=False, verbose=False,
**kwargs): **kwargs):
print('Loading data') print('Loading data')
Ads, b = loadc_Ads_b(fns_in, corner, ico=True) Ads, b = loadc_Ads_b(fns_in, corner)
# Remove duplicate rows # Remove duplicate rows
# is this necessary? # is this necessary?
@ -193,7 +196,7 @@ def run(
Used primarily for multiple optimization passes, such as different algorithms or additional constraints Used primarily for multiple optimization passes, such as different algorithms or additional constraints
''' '''
if bounds_csv: if bounds_csv:
Ads2, b2 = loadc_Ads_b([bounds_csv], corner, ico=True) Ads2, b2 = loadc_Ads_b([bounds_csv], corner)
bounds = Ads2bounds(Ads2, b2) bounds = Ads2bounds(Ads2, b2)
assert len(bounds), 'Failed to load bounds' assert len(bounds), 'Failed to load bounds'
rows_old = len(Ads) rows_old = len(Ads)

View File

@ -23,7 +23,31 @@ corner2minmax = {
} }
def build_tilejo(fnins): 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": { "tiles": {
@ -38,36 +62,37 @@ def build_tilejo(fnins):
], ],
''' '''
tilejo = {"tiles": {}} 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: for fnin in fnins:
tileji = json.load(open(fnin, 'r')) tileji = json.load(open(fnin, 'r'))
for tilek, tilevi in tileji['tiles'].items():
# No previous data? Copy
tilevo = tilejo['tiles'].get(tilek, None)
if tilevo is None:
tilejo['tiles'][tilek] = tilevi
# Otherwise combine
else:
def process_type(etype): merge_tiles(tileji['tiles'], tilejo['tiles'])
for pipk, pipvi in tilevi[etype].items(): merge_sites(tileji['sites'], tilejo['sites'])
pipvo = tilevo[etype][pipk]
for cornerk, corneri in corner_s2i.items():
cornervo = pipvo[corneri]
cornervi = pipvi[corneri]
# no new data
if cornervi is None:
pass
# no previous data
elif cornervo is None:
pipvo[corneri] = cornervi
# combine
else:
minmax = corner2minmax[cornerk]
pipvo[corneri] = minmax(cornervi, cornervo)
process_type('pips')
process_type('wires')
return tilejo return tilejo
@ -110,15 +135,11 @@ def check_corner_minmax(tilej, verbose=False):
timfuz.tilej_stats(tilej) timfuz.tilej_stats(tilej)
def check_corners_minmax(tilej, verbose=False):
# TODO: check fast vs slow
pass
def run(fnins, fnout, verbose=False): def run(fnins, fnout, verbose=False):
tilejo = build_tilejo(fnins) tilejo = build_tilejo(fnins)
check_corner_minmax(tilejo) check_corner_minmax(tilejo)
check_corners_minmax(tilejo) # XXX: check fast vs slow?
# check_corners_minmax(tilejo)
json.dump( json.dump(
tilejo, tilejo,
open(fnout, 'w'), open(fnout, 'w'),

View File

@ -1,293 +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
# prefix to make easier to track
# models do not overlap between PIPs and WIREs
PREFIX_W = 'WIRE_'
PREFIX_P = 'PIP_'
def parse_pip(s):
# Entries like
# CLK_BUFG_REBUF_X60Y117/CLK_BUFG_REBUF.CLK_BUFG_REBUF_R_CK_GCLK0_BOT<<->>CLK_BUFG_REBUF_R_CK_GCLK0_TOP
# Convert to (site, type, pip_junction, pip)
pipstr, speed_index = s.split(':')
speed_index = int(speed_index)
site, instance = pipstr.split('/')
#type, pip_junction, pip = others.split('.')
#return (site, type, pip_junction, pip)
return site, instance, int(speed_index)
def parse_node(s):
node, nwires = s.split(':')
return node, int(nwires)
def parse_wire(s):
# CLBLM_R_X3Y80/CLBLM_M_D6:952
wirestr, speed_index = s.split(':')
site, instance = wirestr.split('/')
return site, instance, int(speed_index)
# FIXME: these actually have a delay element
# Probably need to put these back in
def remove_virtual_pips(pips):
return pips
return filter(lambda pip: not re.match(r'CLBL[LM]_[LR]_', pip[0]), pips)
def load_timing3(f, name='file'):
# src_bel dst_bel ico fast_max fast_min slow_max slow_min pips
f.readline()
ret = []
bads = 0
for l in f:
# FIXME: hack
if 0 and 'CLK' in l:
continue
l = l.strip()
if not l:
continue
parts = l.split(' ')
# FIXME: deal with these nodes
if len(parts) != 11:
bads += 1
continue
net, src_bel, dst_bel, ico, fast_max, fast_min, slow_max, slow_min, pips, nodes, wires = parts
pips = pips.split('|')
nodes = nodes.split('|')
wires = wires.split('|')
ret.append(
{
'net': net,
'src_bel': src_bel,
'dst_bel': dst_bel,
'ico': int(ico),
# ps
'fast_max': int(fast_max),
'fast_min': int(fast_min),
'slow_max': int(slow_max),
'slow_min': int(slow_min),
'pips': remove_virtual_pips([parse_pip(pip) for pip in pips]),
'nodes': [parse_node(node) for node in nodes],
'wires': [parse_wire(wire) for wire in wires],
'line': l,
})
print(' load %s: %d bad, %d good' % (name, bads, len(ret)))
#assert 0
return ret
def load_speed_json(f):
j = json.load(f)
# Index speed indexes to names
speed_i2s = {}
for k, v in j['speed_model'].items():
i = v['speed_index']
if i != SI_NONE:
speed_i2s[i] = k
return j, speed_i2s
# Verify the nodes and wires really do line up
def vals2Adi_check(vals, names):
print('Checking')
for val in vals:
node_wires = 0
for _node, wiresn in val['nodes']:
node_wires += wiresn
assert node_wires == len(val['wires'])
print('Done')
assert 0
def vals2Adi(vals, speed_i2s, name_tr={}, name_drop=[], verbose=False):
def pip2speed(pip):
_site, _name, speed_index = pip
return PREFIX_P + speed_i2s[speed_index]
def wire2speed(wire):
_site, _name, speed_index = wire
return PREFIX_W + speed_i2s[speed_index]
# Want this ordered
names = OrderedDict()
print(
'Creating matrix w/ tr: %d, drop: %d' % (len(name_tr), len(name_drop)))
# Take sites out entirely using handy "interconnect only" option
#vals = filter(lambda x: str(x).find('SLICE') >= 0, vals)
# Highest count while still getting valid result
# First index all of the given pip types
# Start out as set then convert to list to keep matrix order consistent
sys.stdout.write('Indexing delay elements ')
sys.stdout.flush()
progress = max(1, len(vals) / 100)
for vali, val in enumerate(vals):
if vali % progress == 0:
sys.stdout.write('.')
sys.stdout.flush()
odl = [(pip2speed(pip), None) for pip in val['pips']]
names.update(OrderedDict(odl))
odl = [(wire2speed(wire), None) for wire in val['wires']]
names.update(OrderedDict(odl))
print(' done')
# Apply transform
orig_names = len(names)
for k in (list(name_drop) + list(name_tr.keys())):
if k in names:
del names[k]
else:
print('WARNING: failed to remove %s' % k)
names.update(OrderedDict([(name, None) for name in name_tr.values()]))
print('Names tr %d => %d' % (orig_names, len(names)))
# Make unique list
names = list(names.keys())
name_s2i = {}
for namei, name in enumerate(names):
name_s2i[name] = namei
if verbose:
for name in names:
print('NAME: ', name)
for name in name_drop:
print('DROP: ', name)
for l, r in name_tr.items():
print('TR: %s => %s' % (l, r))
# Now create a matrix with all of these delays
# Each row needs len(names) elements
# -2 means 2 elements present, 0 means absent
# (could hit same pip twice)
print('Creating delay element matrix w/ %d names' % len(names))
Adi = [None for _i in range(len(vals))]
for vali, val in enumerate(vals):
def add_name(name):
if name in name_drop:
return
name = name_tr.get(name, name)
namei = name_s2i[name]
row_di[namei] = row_di.get(namei, 0) + 1
# Start with 0 occurances
#row = [0 for _i in range(len(names))]
row_di = {}
#print('pips: ', val['pips']
for pip in val['pips']:
add_name(pip2speed(pip))
for wire in val['wires']:
add_name(wire2speed(wire))
#A_ub.append(row)
Adi[vali] = row_di
return Adi, names
# TODO: load directly as Ads
# remove names_tr, names_drop
def vals2Ads(vals, speed_i2s, verbose=False):
Adi, names = vals2Adi(vals, speed_i2s, verbose=False)
return A_di2ds(Adi, names)
def load_Ads(speed_json_f, f_ins):
print('Loading data')
_speedj, speed_i2s = load_speed_json(speed_json_f)
vals = []
for avals in [load_timing3(f_in, name) for f_in, name in f_ins]:
vals.extend(avals)
Ads = vals2Ads(vals, speed_i2s)
def mkb(val):
return (
val['fast_max'], val['fast_min'], val['slow_max'], val['slow_min'])
b = [mkb(val) for val in vals]
ico = [val['ico'] for val in vals]
return Ads, b, ico
def run(speed_json_f, fout, f_ins, verbose=0, corner=None):
Ads, bs, ico = load_Ads(speed_json_f, f_ins)
fout.write('ico,fast_max fast_min slow_max slow_min,rows...\n')
for row_bs, row_ds, row_ico in zip(bs, Ads, ico):
# like: 123 456 120 450, 1 a, 2 b
# first column has delay corners, followed by delay element count
items = [str(row_ico), ' '.join([str(x) for x in row_bs])]
for k, v in sorted(row_ds.items()):
items.append('%u %s' % (v, k))
fout.write(','.join(items) + '\n')
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Convert obscure timing3.txt into more readable but roughly equivilent timing3.csv'
)
parser.add_argument('--verbose', type=int, help='')
# made a bulk conversion easier...keep?
parser.add_argument(
'--auto-name', action='store_true', help='timing3.txt => timing3.csv')
parser.add_argument(
'--speed-json',
default='build_speed/speed.json',
help='Provides speed index to name translation')
parser.add_argument('--out', default=None, help='Output timing3.csv file')
parser.add_argument('fns_in', nargs='+', help='Input timing3.txt files')
args = parser.parse_args()
bench = Benchmark()
fnout = args.out
if fnout is None:
if args.auto_name:
assert len(args.fns_in) == 1
fnin = args.fns_in[0]
fnout = fnin.replace('.txt', '.csv')
assert fnout != fnin, 'Expect .txt in'
else:
# practically there are too many stray prints to make this work as expected
assert 0, 'File name required'
fnout = '/dev/stdout'
print("Writing to %s" % fnout)
fout = open(fnout, 'w')
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing3.txt')
run(
speed_json_f=open(args.speed_json, 'r'),
fout=fout,
f_ins=[(open(fn_in, 'r'), fn_in) for fn_in in fns_in],
verbose=args.verbose)
if __name__ == '__main__':
main()

View File

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

View File

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

View File

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

View File

@ -7,8 +7,9 @@ fi
set -ex set -ex
test $# = 1 # for some reason on sourced script set -e doesn't work
test ! -e $1 test $# = 1 || exit 1
mkdir $1 test ! -e "$1"
cd $1 mkdir "$1"
cd "$1"