diff --git a/fuzzers/007-timing/Makefile b/fuzzers/007-timing/Makefile index 5734d78a..20da5da5 100644 --- a/fuzzers/007-timing/Makefile +++ b/fuzzers/007-timing/Makefile @@ -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 diff --git a/fuzzers/007-timing/checksub.py b/fuzzers/007-timing/checksub.py index 86823805..34ab981a 100644 --- a/fuzzers/007-timing/checksub.py +++ b/fuzzers/007-timing/checksub.py @@ -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: diff --git a/fuzzers/007-timing/corner_csv.py b/fuzzers/007-timing/corner_csv.py index a4ccbf84..8b7290a3 100644 --- a/fuzzers/007-timing/corner_csv.py +++ b/fuzzers/007-timing/corner_csv.py @@ -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) diff --git a/fuzzers/007-timing/csv_flat2group.py b/fuzzers/007-timing/csv_flat2group.py index a0f76f1d..d382e6f1 100644 --- a/fuzzers/007-timing/csv_flat2group.py +++ b/fuzzers/007-timing/csv_flat2group.py @@ -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) diff --git a/fuzzers/007-timing/csv_group2flat.py b/fuzzers/007-timing/csv_group2flat.py index c6adf0c6..6f1e31b1 100644 --- a/fuzzers/007-timing/csv_group2flat.py +++ b/fuzzers/007-timing/csv_group2flat.py @@ -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() diff --git a/fuzzers/007-timing/minitest/test_cell_opins.tcl b/fuzzers/007-timing/minitest/test_cell_opins.tcl new file mode 100644 index 00000000..a62fb407 --- /dev/null +++ b/fuzzers/007-timing/minitest/test_cell_opins.tcl @@ -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" + diff --git a/fuzzers/007-timing/minitest/test_linprog.py b/fuzzers/007-timing/minitest/test_linprog.py new file mode 100644 index 00000000..78c8c24a --- /dev/null +++ b/fuzzers/007-timing/minitest/test_linprog.py @@ -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() diff --git a/fuzzers/007-timing/test_unique/.gitignore b/fuzzers/007-timing/minitest/test_unique/.gitignore similarity index 100% rename from fuzzers/007-timing/test_unique/.gitignore rename to fuzzers/007-timing/minitest/test_unique/.gitignore diff --git a/fuzzers/007-timing/test_unique/Makefile b/fuzzers/007-timing/minitest/test_unique/Makefile similarity index 100% rename from fuzzers/007-timing/test_unique/Makefile rename to fuzzers/007-timing/minitest/test_unique/Makefile diff --git a/fuzzers/007-timing/test_unique/README.md b/fuzzers/007-timing/minitest/test_unique/README.md similarity index 100% rename from fuzzers/007-timing/test_unique/README.md rename to fuzzers/007-timing/minitest/test_unique/README.md diff --git a/fuzzers/007-timing/test_unique/generate.sh b/fuzzers/007-timing/minitest/test_unique/generate.sh similarity index 100% rename from fuzzers/007-timing/test_unique/generate.sh rename to fuzzers/007-timing/minitest/test_unique/generate.sh diff --git a/fuzzers/007-timing/test_unique/generate.tcl b/fuzzers/007-timing/minitest/test_unique/generate.tcl similarity index 100% rename from fuzzers/007-timing/test_unique/generate.tcl rename to fuzzers/007-timing/minitest/test_unique/generate.tcl diff --git a/fuzzers/007-timing/test_unique/node_unique.py b/fuzzers/007-timing/minitest/test_unique/node_unique.py similarity index 100% rename from fuzzers/007-timing/test_unique/node_unique.py rename to fuzzers/007-timing/minitest/test_unique/node_unique.py diff --git a/fuzzers/007-timing/test_unique/perf_test.py b/fuzzers/007-timing/minitest/test_unique/perf_test.py similarity index 100% rename from fuzzers/007-timing/test_unique/perf_test.py rename to fuzzers/007-timing/minitest/test_unique/perf_test.py diff --git a/fuzzers/007-timing/test_unique/pip_unique.py b/fuzzers/007-timing/minitest/test_unique/pip_unique.py similarity index 100% rename from fuzzers/007-timing/test_unique/pip_unique.py rename to fuzzers/007-timing/minitest/test_unique/pip_unique.py diff --git a/fuzzers/007-timing/test_unique/top.v b/fuzzers/007-timing/minitest/test_unique/top.v similarity index 100% rename from fuzzers/007-timing/test_unique/top.v rename to fuzzers/007-timing/minitest/test_unique/top.v diff --git a/fuzzers/007-timing/test_unique/wire_unique.py b/fuzzers/007-timing/minitest/test_unique/wire_unique.py similarity index 100% rename from fuzzers/007-timing/test_unique/wire_unique.py rename to fuzzers/007-timing/minitest/test_unique/wire_unique.py diff --git a/fuzzers/007-timing/test_zero/README.md b/fuzzers/007-timing/minitest/test_zero/README.md similarity index 100% rename from fuzzers/007-timing/test_zero/README.md rename to fuzzers/007-timing/minitest/test_zero/README.md diff --git a/fuzzers/007-timing/test_zero/process.py b/fuzzers/007-timing/minitest/test_zero/process.py similarity index 100% rename from fuzzers/007-timing/test_zero/process.py rename to fuzzers/007-timing/minitest/test_zero/process.py diff --git a/fuzzers/007-timing/projects/corner.mk b/fuzzers/007-timing/projects/corner.mk index da62dbd8..d7b27a83 100644 --- a/fuzzers/007-timing/projects/corner.mk +++ b/fuzzers/007-timing/projects/corner.mk @@ -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 diff --git a/fuzzers/007-timing/projects/generate.sh b/fuzzers/007-timing/projects/generate.sh index f8e6e094..a43fadcf 100644 --- a/fuzzers/007-timing/projects/generate.sh +++ b/fuzzers/007-timing/projects/generate.sh @@ -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 } diff --git a/fuzzers/007-timing/projects/is.mk b/fuzzers/007-timing/projects/is.mk new file mode 100644 index 00000000..5c419487 --- /dev/null +++ b/fuzzers/007-timing/projects/is.mk @@ -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) + diff --git a/fuzzers/007-timing/projects/oneblinkw/generate.tcl b/fuzzers/007-timing/projects/oneblinkw/generate.tcl index e7946556..19a3b799 100644 --- a/fuzzers/007-timing/projects/oneblinkw/generate.tcl +++ b/fuzzers/007-timing/projects/oneblinkw/generate.tcl @@ -43,5 +43,5 @@ proc build_design {} { } build_design -write_info3 +write_info4 diff --git a/fuzzers/007-timing/projects/picorv32/generate.tcl b/fuzzers/007-timing/projects/picorv32/generate.tcl index a928a3bd..56a2ebea 100644 --- a/fuzzers/007-timing/projects/picorv32/generate.tcl +++ b/fuzzers/007-timing/projects/picorv32/generate.tcl @@ -44,5 +44,5 @@ proc build_design {} { } build_design -write_info3 +write_info4 diff --git a/fuzzers/007-timing/projects/placelut/generate.sh b/fuzzers/007-timing/projects/placelut/generate.sh index fff71d31..40bc4550 100755 --- a/fuzzers/007-timing/projects/placelut/generate.sh +++ b/fuzzers/007-timing/projects/placelut/generate.sh @@ -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 diff --git a/fuzzers/007-timing/projects/placelut/generate.tcl b/fuzzers/007-timing/projects/placelut/generate.tcl index 0b536169..11152586 100644 --- a/fuzzers/007-timing/projects/placelut/generate.tcl +++ b/fuzzers/007-timing/projects/placelut/generate.tcl @@ -43,5 +43,5 @@ proc build_design {} { } build_design -write_info3 +write_info4 diff --git a/fuzzers/007-timing/projects/placelut_fb/generate.tcl b/fuzzers/007-timing/projects/placelut_fb/generate.tcl index 0b536169..11152586 100644 --- a/fuzzers/007-timing/projects/placelut_fb/generate.tcl +++ b/fuzzers/007-timing/projects/placelut_fb/generate.tcl @@ -43,5 +43,5 @@ proc build_design {} { } build_design -write_info3 +write_info4 diff --git a/fuzzers/007-timing/projects/placelut_ff_fb/generate.tcl b/fuzzers/007-timing/projects/placelut_ff_fb/generate.tcl index 0b536169..11152586 100644 --- a/fuzzers/007-timing/projects/placelut_ff_fb/generate.tcl +++ b/fuzzers/007-timing/projects/placelut_ff_fb/generate.tcl @@ -43,5 +43,5 @@ proc build_design {} { } build_design -write_info3 +write_info4 diff --git a/fuzzers/007-timing/projects/project.mk b/fuzzers/007-timing/projects/project.mk index 4cced328..f21f803e 100644 --- a/fuzzers/007-timing/projects/project.mk +++ b/fuzzers/007-timing/projects/project.mk @@ -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) diff --git a/fuzzers/007-timing/projects/project.tcl b/fuzzers/007-timing/projects/project.tcl index b1478201..9d45128e 100644 --- a/fuzzers/007-timing/projects/project.tcl +++ b/fuzzers/007-timing/projects/project.tcl @@ -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: +# 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: ) + + +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 - (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 + diff --git a/fuzzers/007-timing/rref.py b/fuzzers/007-timing/rref.py index be13c805..7d6a449b 100644 --- a/fuzzers/007-timing/rref.py +++ b/fuzzers/007-timing/rref.py @@ -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( diff --git a/fuzzers/007-timing/solve_leastsq.py b/fuzzers/007-timing/solve_leastsq.py index 953c4dcf..3e6f5742 100644 --- a/fuzzers/007-timing/solve_leastsq.py +++ b/fuzzers/007-timing/solve_leastsq.py @@ -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: diff --git a/fuzzers/007-timing/solve_linprog.py b/fuzzers/007-timing/solve_linprog.py index 0bb0dd6f..40586f5e 100644 --- a/fuzzers/007-timing/solve_linprog.py +++ b/fuzzers/007-timing/solve_linprog.py @@ -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: diff --git a/fuzzers/007-timing/solve_qor.py b/fuzzers/007-timing/solve_qor.py index d3bb1eea..5a0bcf17 100644 --- a/fuzzers/007-timing/solve_qor.py +++ b/fuzzers/007-timing/solve_qor.py @@ -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( diff --git a/fuzzers/007-timing/tile_annotate.py b/fuzzers/007-timing/tile_annotate.py index af3d3d2a..e989d9a2 100644 --- a/fuzzers/007-timing/tile_annotate.py +++ b/fuzzers/007-timing/tile_annotate.py @@ -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) diff --git a/fuzzers/007-timing/timfuz.py b/fuzzers/007-timing/timfuz.py index 7e5a970e..d380e525 100644 --- a/fuzzers/007-timing/timfuz.py +++ b/fuzzers/007-timing/timfuz.py @@ -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) diff --git a/fuzzers/007-timing/timfuz_massage.py b/fuzzers/007-timing/timfuz_massage.py index 90d318f3..d64853c2 100644 --- a/fuzzers/007-timing/timfuz_massage.py +++ b/fuzzers/007-timing/timfuz_massage.py @@ -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('') diff --git a/fuzzers/007-timing/timfuz_solve.py b/fuzzers/007-timing/timfuz_solve.py index 4cded620..3e09cf07 100644 --- a/fuzzers/007-timing/timfuz_solve.py +++ b/fuzzers/007-timing/timfuz_solve.py @@ -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) diff --git a/fuzzers/007-timing/timgrid_vc2v.py b/fuzzers/007-timing/timgrid_vc2v.py index 8c188d81..8cb19162 100644 --- a/fuzzers/007-timing/timgrid_vc2v.py +++ b/fuzzers/007-timing/timgrid_vc2v.py @@ -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'), diff --git a/fuzzers/007-timing/timing_txt2csv.py b/fuzzers/007-timing/timing_txt2csv.py deleted file mode 100644 index 0ba2b36d..00000000 --- a/fuzzers/007-timing/timing_txt2csv.py +++ /dev/null @@ -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() diff --git a/fuzzers/007-timing/timing_txt2icsv.py b/fuzzers/007-timing/timing_txt2icsv.py new file mode 100644 index 00000000..469df204 --- /dev/null +++ b/fuzzers/007-timing/timing_txt2icsv.py @@ -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() diff --git a/fuzzers/007-timing/timing_txt2json.py b/fuzzers/007-timing/timing_txt2json.py new file mode 100644 index 00000000..4d26a5f6 --- /dev/null +++ b/fuzzers/007-timing/timing_txt2json.py @@ -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 diff --git a/fuzzers/007-timing/timing_txt2scsv.py b/fuzzers/007-timing/timing_txt2scsv.py new file mode 100644 index 00000000..1dedb470 --- /dev/null +++ b/fuzzers/007-timing/timing_txt2scsv.py @@ -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() diff --git a/utils/genheader.sh b/utils/genheader.sh index 7c9bba0f..71766634 100644 --- a/utils/genheader.sh +++ b/utils/genheader.sh @@ -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"