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
# consider instead aggregating multiple projects
PRJ?=picorv32
PRJN?=8
PRJ?=oneblinkw
PRJN?=1
all: build/timgrid-v.json

View File

@ -60,7 +60,7 @@ def pds(Ads, s):
def run(fns_in, sub_json=None, verbose=False):
assert len(fns_in) > 0
# arbitrary corner...data is thrown away
Ads, b = loadc_Ads_b(fns_in, "slow_max", ico=True)
Ads, b = loadc_Ads_b(fns_in, "slow_max")
if sub_json:
print('Subbing JSON %u rows' % len(Ads))
@ -111,14 +111,14 @@ def main():
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--sub-json', help='')
parser.add_argument('fns_in', nargs='*', help='timing3.csv input files')
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_*/timing3.csv')
fns_in = glob.glob('specimen_*/timing4i.csv')
sub_json = None
if args.sub_json:

View File

@ -28,10 +28,12 @@ def main():
parser.add_argument('--verbose', type=int, help='')
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('--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()
bench = Benchmark()
@ -40,8 +42,8 @@ def main():
if args.auto_name:
assert len(args.fns_in) == 1
fnin = args.fns_in[0]
fnout = fnin.replace('timing3.csv', 'timing3c.csv')
assert fnout != fnin, 'Expect timing3.csv in'
fnout = fnin.replace('timing4i.csv', 'timing4c.csv')
assert fnout != fnin, 'Expect timing4i.csv in'
else:
fnout = '/dev/stdout'
print("Writing to %s" % fnout)
@ -49,7 +51,7 @@ def main():
fns_in = args.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)

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):
print('Loading data')
Ads, bs = loadc_Ads_bs([fnin], ico=True)
Ads, bs = loadc_Ads_bs([fnin])
print('Sub: %u rows' % len(Ads))
iold = instances(Ads)
@ -45,14 +45,14 @@ def main():
required=True,
help='Group substitutions to make fully ranked')
parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='+', help='timing3.csv input files')
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_*/timing3.csv')
fns_in = glob.glob('specimen_*/timing4i.csv')
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):
Ads, bs = loadc_Ads_bs(fns_in, ico=True)
Ads, bs = loadc_Ads_bs(fns_in)
bounds = Ads2bounds(Ads, bs)
# Elements with zero delay assigned due to sub group
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
ALLOW_ZERO_EQN?=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
@ -19,7 +20,7 @@ clean:
.PHONY: all run clean
$(BUILD_DIR)/$(CORNER):
mkdir $(BUILD_DIR)/$(CORNER)
mkdir -p $(BUILD_DIR)/$(CORNER)
# parent should have built this
$(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)
# 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
$(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 --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
$(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
$(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
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
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
write_info3
write_info4

View File

@ -44,5 +44,5 @@ proc 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
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
write_info3
write_info4

View File

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

View File

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

View File

@ -4,38 +4,26 @@
N := 1
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
CSVS := $(addsuffix /timing3.csv,$(SPECIMENS))
TIMFUZ_DIR=$(XRAY_DIR)/fuzzers/007-timing
RREF_CORNER=slow_max
# Allow 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
# Set ZERO elements to zero delay (as is expected they should be)
RMZERO?=N
BUILD_DIR?=build
# interconnect
BUILD_DIR_I?=$(BUILD_DIR)/i
# site
BUILD_DIR_S?=$(BUILD_DIR)/s
RREF_ARGS=
ifeq ($(RMZERO),Y)
RREF_ARGS+=--rm-zero
endif
CORNERS=fast_max fast_min slow_max slow_min
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
# 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):
bash generate.sh $(subst /OK,,$@) || (if [ "$(BADPRJ_OK)" != 'Y' ] ; then exit 1; fi; exit 0)
touch $@
@ -52,38 +40,16 @@ clean:
.PHONY: all run clean
# Normally require all projects to complete
# If BADPRJ_OK is allowed, only take projects that were successful
# FIXME: couldn't get call to work
exist_csvs = \
for f in $(CSVS); do \
if [ "$(BADPRJ_OK)" != 'Y' -o -f $$f ] ; then \
echo $$f; \
fi; \
done
$(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)
# 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) ; \
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
$(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)
$(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
.PHONY: i s
$(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)
$(BUILD_DIR)/timgrid-v.json: $(TIMGRID_V_I) $(TIMGRID_V_S)
python3 $(TIMFUZ_DIR)/timgrid_vc2v.py --out $(BUILD_DIR)/timgrid-v.json $(TIMGRID_V_I) $(TIMGRID_V_S)

View File

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

View File

@ -73,7 +73,7 @@ class State(object):
def load(fn_ins, simplify=False, corner=None, rm_zero=False):
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:
zero_names = rm_zero_cols(Ads)
if simplify:
@ -223,13 +223,13 @@ def main():
default='build_speed/speed.json',
help='Provides speed index to name translation')
parser.add_argument('--out', help='Output sub.json substitution result')
parser.add_argument('fns_in', nargs='*', help='timing3.csv input files')
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_*/timing3.csv')
fns_in = glob.glob('specimen_*/timing4i.csv')
try:
run(

View File

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

View File

@ -6,7 +6,7 @@ import numpy as np
def run(fns_in, corner, bounds_csv, verbose=False):
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)
# verify is flattened
@ -37,14 +37,14 @@ def main():
'--bounds-csv',
required=True,
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()
# Store options in dict to ease passing through functions
bench = Benchmark()
fns_in = args.fns_in
if not fns_in:
fns_in = glob.glob('specimen_*/timing3.csv')
fns_in = glob.glob('specimen_*/timing4i.csv')
try:
run(

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
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 os
@ -9,22 +9,117 @@ 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], ico=True)
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))
for tile in tilej['tiles'].values():
pips = tile['pips']
for k, v in pips.items():
pips[k] = bounds.get('PIP_' + v, [None, None, None, None])
wires = tile['wires']
for k, v in wires.items():
wires[k] = bounds.get('WIRE_' + v, [None, None, None, None])
add_pip_wire(tilej, bounds_pw)
add_sites(tilej, bounds_sites)
timfuz.tilej_stats(tilej)

View File

@ -17,6 +17,51 @@ 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):
@ -524,11 +569,14 @@ def loadc_Ads_mkb(fns, mkb, filt):
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]
@ -537,10 +585,14 @@ def loadc_Ads_b(fns, corner, ico=None):
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
@ -730,35 +782,63 @@ def corners2csv(bs):
def tilej_stats(tilej):
stats = {}
for etype in ('pips', 'wires'):
tm = stats.setdefault(etype, {})
tm['net'] = 0
tm['solved'] = [0, 0, 0, 0]
tm['covered'] = [0, 0, 0, 0]
for tile in tilej['tiles'].values():
def tile_stats():
stats = {}
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
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 = stats[etype]['net']
solved = stats[etype]['solved'][corneri]
covered = stats[etype]['covered'][corneri]
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, ico=True):
Ads, b = loadc_Ads_b([bounds_csv], corner, ico=ico)
def load_bounds(bounds_csv, corner):
Ads, b = loadc_Ads_b([bounds_csv], corner)
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
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
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
# (and the system is stable)
col_lim = 15
di = 0
while True:
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)
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:
if n_orig == len(b) and col_lim >= cols or di >= iter_lim:
break
col_lim += col_lim / 5
di += 1
dend = len(b)
print('')
print('Derive net: %d => %d' % (dstart, dend))
print('')

View File

@ -150,7 +150,10 @@ def solve_save(outfn, xvals, names, corner, save_zero=True, verbose=False):
print(
'Wrote: %u / %u constrained delays, %u 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(
@ -165,7 +168,7 @@ def run(
verbose=False,
**kwargs):
print('Loading data')
Ads, b = loadc_Ads_b(fns_in, corner, ico=True)
Ads, b = loadc_Ads_b(fns_in, corner)
# Remove duplicate rows
# is this necessary?
@ -193,7 +196,7 @@ def run(
Used primarily for multiple optimization passes, such as different algorithms or additional constraints
'''
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)
assert len(bounds), 'Failed to load bounds'
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": {
@ -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:
tileji = json.load(open(fnin, 'r'))
for tilek, tilevi in tileji['tiles'].items():
# No previous data? Copy
tilevo = tilejo['tiles'].get(tilek, None)
if tilevo is None:
tilejo['tiles'][tilek] = tilevi
# Otherwise combine
else:
def process_type(etype):
for pipk, pipvi in tilevi[etype].items():
pipvo = tilevo[etype][pipk]
for cornerk, corneri in corner_s2i.items():
cornervo = pipvo[corneri]
cornervi = pipvi[corneri]
# no new data
if cornervi is None:
pass
# no previous data
elif cornervo is None:
pipvo[corneri] = cornervi
# combine
else:
minmax = corner2minmax[cornerk]
pipvo[corneri] = minmax(cornervi, cornervo)
merge_tiles(tileji['tiles'], tilejo['tiles'])
merge_sites(tileji['sites'], tilejo['sites'])
process_type('pips')
process_type('wires')
return tilejo
@ -110,15 +135,11 @@ def check_corner_minmax(tilej, verbose=False):
timfuz.tilej_stats(tilej)
def check_corners_minmax(tilej, verbose=False):
# TODO: check fast vs slow
pass
def run(fnins, fnout, verbose=False):
tilejo = build_tilejo(fnins)
check_corner_minmax(tilejo)
check_corners_minmax(tilejo)
# XXX: check fast vs slow?
# check_corners_minmax(tilejo)
json.dump(
tilejo,
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
test $# = 1
test ! -e $1
mkdir $1
cd $1
# for some reason on sourced script set -e doesn't work
test $# = 1 || exit 1
test ! -e "$1"
mkdir "$1"
cd "$1"