From b6e87124ca988bf5c5bffcbd6b73ef8ceaf7b4b5 Mon Sep 17 00:00:00 2001 From: srivatsankrishnan <91.srivatsan@gmail.com> Date: Mon, 19 Jun 2023 19:43:15 -0400 Subject: [PATCH] Initial commit for ISCA 2023 --- .gitignore | 191 + .gitmodules | 4 + Project_FARSI/.gitignore | 6 + Project_FARSI/BUCKCONFIG_FILE_EXISTS.md | 0 Project_FARSI/CODE_OF_CONDUCT.md | 80 + Project_FARSI/CONTRIBUTING.md | 35 + Project_FARSI/DOCUSAURUS_ENABLED.md | 0 Project_FARSI/DSE_utils/__init__.py | 1 + .../design_space_exploration_handler.py | 630 ++ Project_FARSI/DSE_utils/exhaustive_DSE.py | 1190 ++++ Project_FARSI/DSE_utils/hill_climbing.py | 3870 ++++++++++++ .../DSE_utils/simple_minimal_change_ga.py | 209 + .../EXTERNAL_CONTINUOUS_INTEGRATION_RUNS.md | 0 Project_FARSI/GHANGELOG.md | 1 + Project_FARSI/GITHUB_ISSUE_TEMPLATE_EXISTS.md | 0 Project_FARSI/GROUP_NOTIFICATIONS.md | 0 Project_FARSI/LICENSE | 19 + Project_FARSI/PAGES_ENABLED.md | 0 Project_FARSI/README.md | 133 + Project_FARSI/SHIP_IT_IS_ENABLED.md | 0 Project_FARSI/SHIP_IT_IS_SET_UP.md | 0 Project_FARSI/SIM_utils/SIM.py | 82 + Project_FARSI/SIM_utils/__init__.py | 0 .../SIM_utils/components/__init__.py | 0 .../SIM_utils/components/perf_sim.py | 492 ++ Project_FARSI/SIM_utils/components/pow_sim.py | 21 + Project_FARSI/__init__.py | 0 .../cacti_for_FARSI/2DDRAM_Samsung2GbDDR2.cfg | 194 + .../cacti_for_FARSI/2DDRAM_micron1Gb.cfg | 194 + .../3DDRAM_Samsung3D8Gb_extened.cfg | 197 + Project_FARSI/cacti_for_FARSI/README | 122 + Project_FARSI/cacti_for_FARSI/TSV.cc | 242 + Project_FARSI/cacti_for_FARSI/TSV.h | 96 + Project_FARSI/cacti_for_FARSI/Ucache.cc | 1073 ++++ Project_FARSI/cacti_for_FARSI/Ucache.h | 118 + Project_FARSI/cacti_for_FARSI/arbiter.cc | 130 + Project_FARSI/cacti_for_FARSI/arbiter.h | 77 + Project_FARSI/cacti_for_FARSI/area.cc | 46 + Project_FARSI/cacti_for_FARSI/area.h | 71 + Project_FARSI/cacti_for_FARSI/bank.cc | 206 + Project_FARSI/cacti_for_FARSI/bank.h | 74 + .../cacti_for_FARSI/basic_circuit.cc | 999 ++++ Project_FARSI/cacti_for_FARSI/basic_circuit.h | 305 + Project_FARSI/cacti_for_FARSI/cache.cfg | 305 + Project_FARSI/cacti_for_FARSI/cacti | Bin 0 -> 2605680 bytes Project_FARSI/cacti_for_FARSI/cacti.i | 8 + Project_FARSI/cacti_for_FARSI/cacti.mk | 53 + .../cacti_for_FARSI/cacti_interface.cc | 174 + .../cacti_for_FARSI/cacti_interface.h | 904 +++ Project_FARSI/cacti_for_FARSI/component.cc | 237 + Project_FARSI/cacti_for_FARSI/component.h | 84 + Project_FARSI/cacti_for_FARSI/const.h | 273 + Project_FARSI/cacti_for_FARSI/contention.dat | 126 + Project_FARSI/cacti_for_FARSI/crossbar.cc | 161 + Project_FARSI/cacti_for_FARSI/crossbar.h | 83 + Project_FARSI/cacti_for_FARSI/ddr3.cfg | 254 + Project_FARSI/cacti_for_FARSI/decoder.cc | 1673 ++++++ Project_FARSI/cacti_for_FARSI/decoder.h | 272 + Project_FARSI/cacti_for_FARSI/dram.cfg | 114 + Project_FARSI/cacti_for_FARSI/extio.cc | 506 ++ Project_FARSI/cacti_for_FARSI/extio.h | 46 + .../cacti_for_FARSI/extio_technology.cc | 1617 +++++ .../cacti_for_FARSI/extio_technology.h | 225 + Project_FARSI/cacti_for_FARSI/farsi_gen.cfg | 254 + Project_FARSI/cacti_for_FARSI/htree2.cc | 640 ++ Project_FARSI/cacti_for_FARSI/htree2.h | 97 + Project_FARSI/cacti_for_FARSI/io.cc | 3790 ++++++++++++ Project_FARSI/cacti_for_FARSI/io.h | 45 + Project_FARSI/cacti_for_FARSI/lpddr.cfg | 254 + Project_FARSI/cacti_for_FARSI/main.cc | 270 + Project_FARSI/cacti_for_FARSI/makefile | 28 + Project_FARSI/cacti_for_FARSI/mat.cc | 1940 ++++++ Project_FARSI/cacti_for_FARSI/mat.h | 176 + Project_FARSI/cacti_for_FARSI/memcad.cc | 599 ++ Project_FARSI/cacti_for_FARSI/memcad.h | 30 + .../cacti_for_FARSI/memcad_parameters.cc | 466 ++ .../cacti_for_FARSI/memcad_parameters.h | 251 + Project_FARSI/cacti_for_FARSI/memorybus.cc | 741 +++ Project_FARSI/cacti_for_FARSI/memorybus.h | 150 + Project_FARSI/cacti_for_FARSI/nuca.cc | 611 ++ Project_FARSI/cacti_for_FARSI/nuca.h | 102 + Project_FARSI/cacti_for_FARSI/obj_dbg/TSV.o | Bin 0 -> 146888 bytes .../cacti_for_FARSI/obj_dbg/Ucache.o | Bin 0 -> 508352 bytes .../cacti_for_FARSI/obj_dbg/arbiter.o | Bin 0 -> 174808 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/area.o | Bin 0 -> 48416 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/bank.o | Bin 0 -> 194928 bytes .../cacti_for_FARSI/obj_dbg/basic_circuit.o | Bin 0 -> 164688 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/cacti | Bin 0 -> 2605680 bytes .../cacti_for_FARSI/obj_dbg/cacti_interface.o | Bin 0 -> 183040 bytes .../cacti_for_FARSI/obj_dbg/component.o | Bin 0 -> 140448 bytes .../cacti_for_FARSI/obj_dbg/crossbar.o | Bin 0 -> 179680 bytes .../cacti_for_FARSI/obj_dbg/decoder.o | Bin 0 -> 218848 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/extio.o | Bin 0 -> 144344 bytes .../obj_dbg/extio_technology.o | Bin 0 -> 188048 bytes .../cacti_for_FARSI/obj_dbg/htree2.o | Bin 0 -> 203520 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/io.o | Bin 0 -> 454608 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/main.o | Bin 0 -> 207696 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/mat.o | Bin 0 -> 317936 bytes .../cacti_for_FARSI/obj_dbg/memcad.o | Bin 0 -> 716632 bytes .../obj_dbg/memcad_parameters.o | Bin 0 -> 295552 bytes .../cacti_for_FARSI/obj_dbg/memorybus.o | Bin 0 -> 247528 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/nuca.o | Bin 0 -> 305744 bytes .../cacti_for_FARSI/obj_dbg/parameter.o | Bin 0 -> 342688 bytes .../cacti_for_FARSI/obj_dbg/powergating.o | Bin 0 -> 135048 bytes .../cacti_for_FARSI/obj_dbg/router.o | Bin 0 -> 215112 bytes .../cacti_for_FARSI/obj_dbg/subarray.o | Bin 0 -> 145504 bytes .../cacti_for_FARSI/obj_dbg/technology.o | Bin 0 -> 141200 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/uca.o | Bin 0 -> 276528 bytes Project_FARSI/cacti_for_FARSI/obj_dbg/wire.o | Bin 0 -> 270056 bytes Project_FARSI/cacti_for_FARSI/parameter.cc | 2837 +++++++++ Project_FARSI/cacti_for_FARSI/parameter.h | 779 +++ Project_FARSI/cacti_for_FARSI/powergating.cc | 129 + Project_FARSI/cacti_for_FARSI/powergating.h | 86 + Project_FARSI/cacti_for_FARSI/regression.test | 45 + Project_FARSI/cacti_for_FARSI/router.cc | 311 + Project_FARSI/cacti_for_FARSI/router.h | 115 + .../sample_config_files/ddr3_cache.cfg | 259 + .../sample_config_files/diff_ddr3_cache.cfg | 259 + .../sample_config_files/lpddr3_cache.cfg | 259 + .../sample_config_files/wideio_cache.cfg | 259 + Project_FARSI/cacti_for_FARSI/subarray.cc | 205 + Project_FARSI/cacti_for_FARSI/subarray.h | 70 + .../cacti_for_FARSI/tech_params/16nm.dat | 1 + .../cacti_for_FARSI/tech_params/180nm-old.dat | 294 + .../cacti_for_FARSI/tech_params/180nm.dat | 113 + .../cacti_for_FARSI/tech_params/22nm.dat | 113 + .../cacti_for_FARSI/tech_params/32nm.dat | 113 + .../cacti_for_FARSI/tech_params/45nm.dat | 113 + .../cacti_for_FARSI/tech_params/65nm-old.dat | 301 + .../cacti_for_FARSI/tech_params/65nm.dat | 113 + .../cacti_for_FARSI/tech_params/90nm-old.dat | 301 + .../cacti_for_FARSI/tech_params/90nm.dat | 113 + Project_FARSI/cacti_for_FARSI/technology.cc | 288 + Project_FARSI/cacti_for_FARSI/uca.cc | 818 +++ Project_FARSI/cacti_for_FARSI/uca.h | 116 + Project_FARSI/cacti_for_FARSI/version_cacti.h | 40 + Project_FARSI/cacti_for_FARSI/wire.cc | 830 +++ Project_FARSI/cacti_for_FARSI/wire.h | 123 + .../collection_utils/home_settings.py | 9 + .../collection_utils/replay/FARSI_replay.py | 59 + .../collection_utils/replay/replayer.py | 106 + .../sim_run/exploration_time.png | Bin 0 -> 45517 bytes .../sim_run/sim_run_array_x.py | 138 + .../sim_run/simple_sim_run.py | 118 + .../what_ifs/FARSI_what_ifs.py | 893 +++ .../what_ifs/FARSI_what_ifs_simple.py | 60 + .../what_ifs/FARSI_what_ifs_with_params.py | 352 ++ .../collection_utils/what_ifs/autoWLandDep.py | 13 + Project_FARSI/design_utils/__init__.py | 0 .../design_utils/common_design_utils.py | 34 + .../design_utils/components/__init__.py | 0 .../design_utils/components/hardware.py | 1841 ++++++ .../design_utils/components/krnel.py | 1932 ++++++ .../design_utils/components/mapping.py | 388 ++ .../design_utils/components/scheduling.py | 81 + .../design_utils/components/workload.py | 597 ++ Project_FARSI/design_utils/des_handler.py | 2539 ++++++++ Project_FARSI/design_utils/design.py | 3014 ++++++++++ Project_FARSI/error_handling/custom_error.py | 164 + Project_FARSI/figures/DSE_on_the_map.pdf | Bin 0 -> 75744 bytes Project_FARSI/figures/DSE_on_the_map.png | Bin 0 -> 114029 bytes Project_FARSI/figures/FARSI_methodology.pdf | Bin 0 -> 55219 bytes Project_FARSI/figures/FARSI_methodology.png | Bin 0 -> 79323 bytes Project_FARSI/figures/FARSI_output.pdf | Bin 0 -> 77831 bytes Project_FARSI/figures/FARSI_output.png | Bin 0 -> 117418 bytes Project_FARSI/misc/__init__.py | 0 Project_FARSI/misc/cacti_hndlr/cact_handlr.py | 199 + Project_FARSI/misc/cacti_hndlr/ddr3_.cfg | 254 + Project_FARSI/misc/converters/dot_to_png.py | 23 + Project_FARSI/misc/gotchas | 1 + Project_FARSI/misc/scratch/__init__.py | 0 Project_FARSI/readme | 21 + Project_FARSI/settings/__init__.py | 0 Project_FARSI/settings/config.py | 320 + Project_FARSI/settings/config_cacti.py | 18 + Project_FARSI/settings/config_plotting.py | 34 + Project_FARSI/specs/LW_cl.py | 285 + Project_FARSI/specs/__init__.py | 0 Project_FARSI/specs/data_base.py | 925 +++ .../specs/database_data/generate/input.py | 55 + .../specs/database_data/hardcoded_test.txt | 1 + .../SOC_example_1p_2r_database - Budget.csv | 3 + ...xample_1p_2r_database - Hardware Graph.csv | 5 + ...le_1p_2r_database - Task Data Movement.csv | 4 + ...xample_1p_2r_database - Task Itr Count.csv | 4 + ..._example_1p_2r_database - Task PE Area.csv | 4 + ...xample_1p_2r_database - Task PE Energy.csv | 4 + ...e_1p_2r_database - Task PE Performance.csv | 4 + ...2r_database - Task to Hardware Mapping.csv | 4 + .../SOC_example_1p_database - Budget.csv | 3 + ...C_example_1p_database - Hardware Graph.csv | 4 + ...ample_1p_database - Task Data Movement.csv | 4 + ...C_example_1p_database - Task Itr Count.csv | 4 + ...SOC_example_1p_database - Task PE Area.csv | 4 + ...C_example_1p_database - Task PE Energy.csv | 4 + ...mple_1p_database - Task PE Performance.csv | 4 + ...1p_database - Task To Hardware Mapping.csv | 4 + .../SOC_example_8p_database - Budget.csv | 3 + ...C_example_8p_database - Hardware Graph.csv | 11 + ...ample_8p_database - Task Data Movement.csv | 11 + ...C_example_8p_database - Task Itr Count.csv | 11 + ...SOC_example_8p_database - Task PE Area.csv | 11 + ...C_example_8p_database - Task PE Energy.csv | 11 + ...mple_8p_database - Task PE Performance.csv | 11 + ...8p_database - Task To Hardware Mapping.csv | 17 + .../audio_decoder_database - Budget.csv | 3 + ...udio_decoder_database - Hardware Graph.csv | 4 + ..._decoder_database - Task Data Movement.csv | 19 + ...udio_decoder_database - Task Itr Count.csv | 18 + .../audio_decoder_database - Task PE Area.csv | 19 + ...udio_decoder_database - Task PE Energy.csv | 19 + ... - Task PE Performance for 1000 blocks.csv | 19 + ...decoder_database - Task PE Performance.csv | 19 + .../parsing/audio_decoder_database - misc.csv | 2 + ...ection_1_database - Task Data Movement.csv | 9 + ..._detection_1_database - Task Itr Count.csv | 9 + ...ge_detection_1_database - Task PE Area.csv | 9 + ..._detection_1_database - Task PE Energy.csv | 9 + ...ction_1_database - Task PE Performance.csv | 9 + ...ection_2_database - Task Data Movement.csv | 9 + ..._detection_2_database - Task Itr Count.csv | 9 + ...ge_detection_2_database - Task PE Area.csv | 9 + ..._detection_2_database - Task PE Energy.csv | 9 + ...ction_2_database - Task PE Performance.csv | 9 + ...ection_3_database - Task Data Movement.csv | 9 + ..._detection_3_database - Task Itr Count.csv | 9 + ...ge_detection_3_database - Task PE Area.csv | 9 + ..._detection_3_database - Task PE Energy.csv | 9 + ...ction_3_database - Task PE Performance.csv | 9 + ...ection_4_database - Task Data Movement.csv | 9 + ..._detection_4_database - Task Itr Count.csv | 9 + ...ge_detection_4_database - Task PE Area.csv | 9 + ..._detection_4_database - Task PE Energy.csv | 9 + ...ction_4_database - Task PE Performance.csv | 9 + ...etection_database - Task Data Movement.csv | 9 + ...ge_detection_database - Task Itr Count.csv | 9 + ...edge_detection_database - Task PE Area.csv | 9 + ...ge_detection_database - Task PE Energy.csv | 9 + ...tection_database - Task PE Performance.csv | 9 + .../hpvm_cava_database - Hardware Graph.csv | 4 + ...pvm_cava_database - Task Data Movement.csv | 10 + .../hpvm_cava_database - Task Itr Count.csv | 10 + .../hpvm_cava_database - Task PE Area.csv | 10 + .../hpvm_cava_database - Task PE Energy.csv | 10 + ...vm_cava_database - Task PE Performance.csv | 10 + ...va_database - Task To Hardware Mapping.csv | 10 + .../misc_database - Block Characteristics.csv | 21 + ...atabase - Block Characteristics_for_PA.csv | 21 + ...abase - Block Characteristics_for_real.csv | 21 + .../parsing/misc_database - Budget.csv | 14 + .../parsing/misc_database - Budget_for_PA.csv | 16 + .../misc_database - Budget_for_real.csv | 9 + .../misc_database - Budget_individiaul.csv | 8 + .../misc_database - Budget_individually.csv | 15 + .../misc_database - Common Hardware.csv | 15 + .../parsing/misc_database - Last Tasks.csv | 16 + Project_FARSI/specs/database_input.py | 317 + Project_FARSI/specs/gen_synthetic_data.py | 564 ++ .../specs/parse_libraries/__init__.py | 0 .../specs/parse_libraries/parse_library.py | 801 +++ Project_FARSI/top/main_FARSI.py | 194 + .../Iulian_plots/error_analysis_per_app.py | 14 + .../Iulian_plots/plot_err_vs_arch.py | 75 + .../Iulian_plots/plot_sim_vs_lat.py | 28 + .../Iulian_plots/plot_validations.py | 86 + .../Iulian_plots/simtime_vs_lat.py | 15 + Project_FARSI/visualization_utils/__init__.py | 0 Project_FARSI/visualization_utils/plot.py | 566 ++ .../visualization_utils/plot_arrows.py | 48 + .../visualization_utils/plotting-ying.py | 3081 ++++++++++ Project_FARSI/visualization_utils/plotting.py | 5179 +++++++++++++++++ .../visualization_utils/plotting_Iulian.py | 514 ++ .../visualization_utils/vis_hardware.py | 250 + Project_FARSI/visualization_utils/vis_sim.py | 405 ++ .../visualization_utils/vis_stats.py | 34 + acme/.gitignore | 143 + acme/.pylintrc | 424 ++ acme/.readthedocs.yaml | 8 + acme/CONTRIBUTING.md | 28 + acme/LICENSE | 202 + acme/MANIFEST.in | 1 + acme/README.md | 118 + acme/acme/__init__.py | 38 + acme/acme/_metadata.py | 27 + acme/acme/adders/__init__.py | 21 + acme/acme/adders/base.py | 82 + acme/acme/adders/reverb/__init__.py | 29 + acme/acme/adders/reverb/base.py | 253 + acme/acme/adders/reverb/episode.py | 151 + acme/acme/adders/reverb/episode_test.py | 111 + acme/acme/adders/reverb/sequence.py | 296 + acme/acme/adders/reverb/sequence_test.py | 68 + acme/acme/adders/reverb/structured.py | 424 ++ acme/acme/adders/reverb/structured_test.py | 186 + acme/acme/adders/reverb/test_cases.py | 847 +++ acme/acme/adders/reverb/test_utils.py | 233 + acme/acme/adders/reverb/transition.py | 307 + acme/acme/adders/reverb/transition_test.py | 43 + acme/acme/adders/reverb/utils.py | 96 + acme/acme/adders/wrappers.py | 62 + acme/acme/agents/__init__.py | 15 + acme/acme/agents/agent.py | 136 + acme/acme/agents/jax/__init__.py | 15 + acme/acme/agents/jax/actor_core.py | 170 + acme/acme/agents/jax/actors.py | 99 + acme/acme/agents/jax/actors_test.py | 142 + acme/acme/agents/jax/ail/README.md | 17 + acme/acme/agents/jax/ail/__init__.py | 31 + acme/acme/agents/jax/ail/builder.py | 331 ++ acme/acme/agents/jax/ail/builder_test.py | 56 + acme/acme/agents/jax/ail/config.py | 78 + acme/acme/agents/jax/ail/dac.py | 65 + acme/acme/agents/jax/ail/gail.py | 52 + acme/acme/agents/jax/ail/learning.py | 293 + acme/acme/agents/jax/ail/learning_test.py | 98 + acme/acme/agents/jax/ail/losses.py | 236 + acme/acme/agents/jax/ail/losses_test.py | 79 + acme/acme/agents/jax/ail/networks.py | 399 ++ acme/acme/agents/jax/ail/rewards.py | 76 + acme/acme/agents/jax/ars/README.md | 7 + acme/acme/agents/jax/ars/__init__.py | 20 + acme/acme/agents/jax/ars/builder.py | 160 + acme/acme/agents/jax/ars/config.py | 31 + acme/acme/agents/jax/ars/learning.py | 282 + acme/acme/agents/jax/ars/networks.py | 52 + acme/acme/agents/jax/bc/README.md | 20 + acme/acme/agents/jax/bc/__init__.py | 29 + acme/acme/agents/jax/bc/agent_test.py | 191 + acme/acme/agents/jax/bc/builder.py | 113 + acme/acme/agents/jax/bc/config.py | 28 + acme/acme/agents/jax/bc/learning.py | 200 + acme/acme/agents/jax/bc/losses.py | 143 + acme/acme/agents/jax/bc/networks.py | 119 + acme/acme/agents/jax/bc/pretraining.py | 63 + acme/acme/agents/jax/bc/pretraining_test.py | 94 + acme/acme/agents/jax/builders.py | 226 + acme/acme/agents/jax/cql/README.md | 7 + acme/acme/agents/jax/cql/__init__.py | 21 + acme/acme/agents/jax/cql/agent_test.py | 62 + acme/acme/agents/jax/cql/builder.py | 109 + acme/acme/agents/jax/cql/config.py | 58 + acme/acme/agents/jax/cql/learning.py | 481 ++ acme/acme/agents/jax/cql/networks.py | 60 + acme/acme/agents/jax/crr/README.md | 11 + acme/acme/agents/jax/crr/__init__.py | 25 + acme/acme/agents/jax/crr/agent_test.py | 66 + acme/acme/agents/jax/crr/builder.py | 106 + acme/acme/agents/jax/crr/config.py | 34 + acme/acme/agents/jax/crr/learning.py | 260 + acme/acme/agents/jax/crr/losses.py | 99 + acme/acme/agents/jax/crr/networks.py | 86 + acme/acme/agents/jax/d4pg/README.md | 24 + acme/acme/agents/jax/d4pg/__init__.py | 24 + acme/acme/agents/jax/d4pg/builder.py | 173 + acme/acme/agents/jax/d4pg/config.py | 45 + acme/acme/agents/jax/d4pg/learning.py | 247 + acme/acme/agents/jax/d4pg/networks.py | 109 + acme/acme/agents/jax/dqn/README.md | 37 + acme/acme/agents/jax/dqn/__init__.py | 29 + acme/acme/agents/jax/dqn/actor.py | 111 + acme/acme/agents/jax/dqn/builder.py | 182 + acme/acme/agents/jax/dqn/config.py | 85 + acme/acme/agents/jax/dqn/learning.py | 72 + acme/acme/agents/jax/dqn/learning_lib.py | 211 + acme/acme/agents/jax/dqn/losses.py | 325 ++ acme/acme/agents/jax/dqn/rainbow.py | 95 + acme/acme/agents/jax/impala/__init__.py | 22 + acme/acme/agents/jax/impala/acting.py | 104 + acme/acme/agents/jax/impala/builder.py | 207 + acme/acme/agents/jax/impala/config.py | 63 + acme/acme/agents/jax/impala/learning.py | 160 + acme/acme/agents/jax/impala/networks.py | 99 + acme/acme/agents/jax/impala/types.py | 31 + acme/acme/agents/jax/lfd/__init__.py | 23 + acme/acme/agents/jax/lfd/builder.py | 80 + acme/acme/agents/jax/lfd/config.py | 37 + acme/acme/agents/jax/lfd/lfd_adder.py | 117 + acme/acme/agents/jax/lfd/lfd_adder_test.py | 143 + acme/acme/agents/jax/lfd/sacfd.py | 47 + acme/acme/agents/jax/lfd/td3fd.py | 47 + acme/acme/agents/jax/mbop/README.md | 16 + acme/acme/agents/jax/mbop/__init__.py | 49 + acme/acme/agents/jax/mbop/acting.py | 193 + acme/acme/agents/jax/mbop/agent_test.py | 92 + acme/acme/agents/jax/mbop/dataset.py | 220 + acme/acme/agents/jax/mbop/dataset_test.py | 194 + acme/acme/agents/jax/mbop/ensemble.py | 166 + acme/acme/agents/jax/mbop/ensemble_test.py | 329 ++ acme/acme/agents/jax/mbop/learning.py | 235 + acme/acme/agents/jax/mbop/losses.py | 126 + acme/acme/agents/jax/mbop/models.py | 141 + acme/acme/agents/jax/mbop/mppi.py | 251 + acme/acme/agents/jax/mbop/mppi_test.py | 155 + acme/acme/agents/jax/mbop/networks.py | 138 + acme/acme/agents/jax/multiagent/__init__.py | 15 + .../jax/multiagent/decentralized/README.md | 15 + .../jax/multiagent/decentralized/__init__.py | 26 + .../jax/multiagent/decentralized/actor.py | 58 + .../jax/multiagent/decentralized/agents.py | 199 + .../multiagent/decentralized/agents_test.py | 60 + .../jax/multiagent/decentralized/builder.py | 196 + .../jax/multiagent/decentralized/config.py | 28 + .../jax/multiagent/decentralized/factories.py | 229 + .../multiagent/decentralized/learner_set.py | 85 + acme/acme/agents/jax/normalization.py | 251 + acme/acme/agents/jax/ppo/README.md | 13 + acme/acme/agents/jax/ppo/__init__.py | 27 + acme/acme/agents/jax/ppo/builder.py | 211 + acme/acme/agents/jax/ppo/config.py | 79 + acme/acme/agents/jax/ppo/learning.py | 460 ++ acme/acme/agents/jax/ppo/networks.py | 358 ++ acme/acme/agents/jax/pwil/README.md | 14 + acme/acme/agents/jax/pwil/__init__.py | 19 + acme/acme/agents/jax/pwil/adder.py | 49 + acme/acme/agents/jax/pwil/builder.py | 203 + acme/acme/agents/jax/pwil/config.py | 55 + acme/acme/agents/jax/pwil/rewarder.py | 157 + acme/acme/agents/jax/r2d2/__init__.py | 25 + acme/acme/agents/jax/r2d2/actor.py | 109 + acme/acme/agents/jax/r2d2/builder.py | 270 + acme/acme/agents/jax/r2d2/config.py | 53 + acme/acme/agents/jax/r2d2/learning.py | 279 + acme/acme/agents/jax/r2d2/networks.py | 91 + acme/acme/agents/jax/rnd/README.md | 12 + acme/acme/agents/jax/rnd/__init__.py | 26 + acme/acme/agents/jax/rnd/builder.py | 135 + acme/acme/agents/jax/rnd/config.py | 30 + acme/acme/agents/jax/rnd/learning.py | 229 + acme/acme/agents/jax/rnd/networks.py | 124 + acme/acme/agents/jax/sac/README.md | 19 + acme/acme/agents/jax/sac/__init__.py | 24 + acme/acme/agents/jax/sac/builder.py | 160 + acme/acme/agents/jax/sac/config.py | 96 + acme/acme/agents/jax/sac/learning.py | 289 + acme/acme/agents/jax/sac/networks.py | 143 + acme/acme/agents/jax/sqil/README.md | 9 + acme/acme/agents/jax/sqil/__init__.py | 17 + acme/acme/agents/jax/sqil/builder.py | 170 + acme/acme/agents/jax/sqil/builder_test.py | 44 + acme/acme/agents/jax/td3/README.md | 13 + acme/acme/agents/jax/td3/__init__.py | 22 + acme/acme/agents/jax/td3/builder.py | 165 + acme/acme/agents/jax/td3/config.py | 60 + acme/acme/agents/jax/td3/learning.py | 333 ++ acme/acme/agents/jax/td3/networks.py | 118 + acme/acme/agents/jax/value_dice/README.md | 12 + acme/acme/agents/jax/value_dice/__init__.py | 22 + acme/acme/agents/jax/value_dice/builder.py | 151 + acme/acme/agents/jax/value_dice/config.py | 45 + acme/acme/agents/jax/value_dice/learning.py | 329 ++ acme/acme/agents/jax/value_dice/networks.py | 100 + acme/acme/agents/replay.py | 168 + acme/acme/agents/tf/__init__.py | 14 + acme/acme/agents/tf/actors.py | 186 + acme/acme/agents/tf/actors_test.py | 72 + acme/acme/agents/tf/bc/README.md | 8 + acme/acme/agents/tf/bc/__init__.py | 17 + acme/acme/agents/tf/bc/learning.py | 123 + acme/acme/agents/tf/bcq/README.md | 8 + acme/acme/agents/tf/bcq/__init__.py | 17 + acme/acme/agents/tf/bcq/discrete_learning.py | 260 + .../agents/tf/bcq/discrete_learning_test.py | 81 + acme/acme/agents/tf/crr/__init__.py | 17 + acme/acme/agents/tf/crr/recurrent_learning.py | 407 ++ acme/acme/agents/tf/d4pg/README.md | 24 + acme/acme/agents/tf/d4pg/__init__.py | 20 + acme/acme/agents/tf/d4pg/agent.py | 463 ++ acme/acme/agents/tf/d4pg/agent_distributed.py | 268 + .../agents/tf/d4pg/agent_distributed_test.py | 84 + acme/acme/agents/tf/d4pg/agent_test.py | 91 + acme/acme/agents/tf/d4pg/learning.py | 372 ++ acme/acme/agents/tf/d4pg/networks.py | 63 + acme/acme/agents/tf/ddpg/README.md | 16 + acme/acme/agents/tf/ddpg/__init__.py | 19 + acme/acme/agents/tf/ddpg/agent.py | 173 + acme/acme/agents/tf/ddpg/agent_distributed.py | 319 + .../agents/tf/ddpg/agent_distributed_test.py | 89 + acme/acme/agents/tf/ddpg/agent_test.py | 82 + acme/acme/agents/tf/ddpg/learning.py | 257 + acme/acme/agents/tf/dmpo/README.md | 32 + acme/acme/agents/tf/dmpo/__init__.py | 19 + acme/acme/agents/tf/dmpo/agent.py | 188 + acme/acme/agents/tf/dmpo/agent_distributed.py | 358 ++ .../agents/tf/dmpo/agent_distributed_test.py | 97 + acme/acme/agents/tf/dmpo/agent_test.py | 83 + acme/acme/agents/tf/dmpo/learning.py | 300 + acme/acme/agents/tf/dqfd/README.md | 7 + acme/acme/agents/tf/dqfd/__init__.py | 18 + acme/acme/agents/tf/dqfd/agent.py | 211 + acme/acme/agents/tf/dqfd/agent_test.py | 75 + .../agents/tf/dqfd/bsuite_demonstrations.py | 135 + acme/acme/agents/tf/dqn/README.md | 20 + acme/acme/agents/tf/dqn/__init__.py | 19 + acme/acme/agents/tf/dqn/agent.py | 177 + acme/acme/agents/tf/dqn/agent_distributed.py | 272 + .../agents/tf/dqn/agent_distributed_test.py | 56 + acme/acme/agents/tf/dqn/agent_test.py | 60 + acme/acme/agents/tf/dqn/learning.py | 238 + acme/acme/agents/tf/impala/README.md | 7 + acme/acme/agents/tf/impala/__init__.py | 20 + acme/acme/agents/tf/impala/acting.py | 96 + acme/acme/agents/tf/impala/agent.py | 123 + .../agents/tf/impala/agent_distributed.py | 230 + .../tf/impala/agent_distributed_test.py | 56 + acme/acme/agents/tf/impala/agent_test.py | 66 + acme/acme/agents/tf/impala/learning.py | 190 + acme/acme/agents/tf/iqn/README.md | 6 + acme/acme/agents/tf/iqn/__init__.py | 17 + acme/acme/agents/tf/iqn/learning.py | 266 + acme/acme/agents/tf/iqn/learning_test.py | 89 + acme/acme/agents/tf/mcts/README.md | 12 + acme/acme/agents/tf/mcts/__init__.py | 18 + acme/acme/agents/tf/mcts/acting.py | 120 + acme/acme/agents/tf/mcts/agent.py | 99 + acme/acme/agents/tf/mcts/agent_distributed.py | 233 + acme/acme/agents/tf/mcts/agent_test.py | 68 + acme/acme/agents/tf/mcts/learning.py | 89 + acme/acme/agents/tf/mcts/models/__init__.py | 19 + acme/acme/agents/tf/mcts/models/base.py | 52 + acme/acme/agents/tf/mcts/models/mlp.py | 220 + acme/acme/agents/tf/mcts/models/simulator.py | 87 + .../agents/tf/mcts/models/simulator_test.py | 90 + acme/acme/agents/tf/mcts/search.py | 194 + acme/acme/agents/tf/mcts/search_test.py | 65 + acme/acme/agents/tf/mcts/types.py | 39 + acme/acme/agents/tf/mog_mpo/README.md | 49 + acme/acme/agents/tf/mog_mpo/__init__.py | 20 + .../agents/tf/mog_mpo/agent_distributed.py | 292 + acme/acme/agents/tf/mog_mpo/learning.py | 317 + acme/acme/agents/tf/mog_mpo/networks.py | 76 + acme/acme/agents/tf/mompo/README.md | 28 + acme/acme/agents/tf/mompo/__init__.py | 21 + acme/acme/agents/tf/mompo/agent.py | 204 + .../acme/agents/tf/mompo/agent_distributed.py | 361 ++ .../agents/tf/mompo/agent_distributed_test.py | 163 + acme/acme/agents/tf/mompo/agent_test.py | 149 + acme/acme/agents/tf/mompo/learning.py | 454 ++ acme/acme/agents/tf/mpo/README.md | 28 + acme/acme/agents/tf/mpo/__init__.py | 19 + acme/acme/agents/tf/mpo/agent.py | 191 + acme/acme/agents/tf/mpo/agent_distributed.py | 338 ++ .../agents/tf/mpo/agent_distributed_test.py | 100 + acme/acme/agents/tf/mpo/agent_test.py | 77 + acme/acme/agents/tf/mpo/learning.py | 287 + acme/acme/agents/tf/r2d2/README.md | 12 + acme/acme/agents/tf/r2d2/__init__.py | 18 + acme/acme/agents/tf/r2d2/agent.py | 152 + acme/acme/agents/tf/r2d2/agent_distributed.py | 276 + .../agents/tf/r2d2/agent_distributed_test.py | 58 + acme/acme/agents/tf/r2d2/agent_test.py | 84 + acme/acme/agents/tf/r2d2/learning.py | 241 + acme/acme/agents/tf/r2d3/README.md | 11 + acme/acme/agents/tf/r2d3/__init__.py | 17 + acme/acme/agents/tf/r2d3/agent.py | 226 + acme/acme/agents/tf/r2d3/agent_test.py | 94 + acme/acme/agents/tf/svg0_prior/README.md | 24 + acme/acme/agents/tf/svg0_prior/__init__.py | 21 + acme/acme/agents/tf/svg0_prior/acting.py | 67 + acme/acme/agents/tf/svg0_prior/agent.py | 370 ++ .../agents/tf/svg0_prior/agent_distributed.py | 251 + .../tf/svg0_prior/agent_distributed_test.py | 95 + acme/acme/agents/tf/svg0_prior/agent_test.py | 96 + acme/acme/agents/tf/svg0_prior/learning.py | 386 ++ acme/acme/agents/tf/svg0_prior/networks.py | 118 + acme/acme/agents/tf/svg0_prior/utils.py | 157 + acme/acme/core.py | 176 + acme/acme/core_test.py | 57 + acme/acme/datasets/__init__.py | 19 + acme/acme/datasets/image_augmentation.py | 120 + acme/acme/datasets/numpy_iterator.py | 48 + acme/acme/datasets/numpy_iterator_test.py | 49 + acme/acme/datasets/reverb.py | 157 + acme/acme/datasets/reverb_benchmark.py | 97 + acme/acme/datasets/tfds.py | 209 + acme/acme/environment_loop.py | 189 + acme/acme/environment_loop_test.py | 102 + acme/acme/environment_loops/__init__.py | 21 + .../open_spiel_environment_loop.py | 227 + .../open_spiel_environment_loop_test.py | 101 + acme/acme/jax/__init__.py | 14 + acme/acme/jax/experiments/__init__.py | 30 + acme/acme/jax/experiments/config.py | 309 + .../make_distributed_experiment.py | 286 + .../make_distributed_offline_experiment.py | 145 + acme/acme/jax/experiments/run_experiment.py | 276 + .../jax/experiments/run_offline_experiment.py | 96 + acme/acme/jax/imitation_learning_types.py | 22 + acme/acme/jax/layouts/__init__.py | 14 + acme/acme/jax/layouts/distributed_layout.py | 193 + acme/acme/jax/layouts/local_layout.py | 153 + .../jax/layouts/offline_distributed_layout.py | 128 + acme/acme/jax/losses/__init__.py | 20 + acme/acme/jax/losses/impala.py | 111 + acme/acme/jax/losses/impala_test.py | 101 + acme/acme/jax/losses/mpo.py | 452 ++ acme/acme/jax/networks/__init__.py | 52 + acme/acme/jax/networks/atari.py | 182 + acme/acme/jax/networks/base.py | 60 + acme/acme/jax/networks/continuous.py | 76 + acme/acme/jax/networks/distributional.py | 330 ++ acme/acme/jax/networks/duelling.py | 60 + acme/acme/jax/networks/embedding.py | 61 + acme/acme/jax/networks/multiplexers.py | 71 + acme/acme/jax/networks/policy_value.py | 36 + acme/acme/jax/networks/rescaling.py | 57 + acme/acme/jax/networks/resnet.py | 159 + acme/acme/jax/running_statistics.py | 355 ++ acme/acme/jax/running_statistics_test.py | 305 + acme/acme/jax/savers.py | 96 + acme/acme/jax/savers_test.py | 89 + acme/acme/jax/snapshotter.py | 116 + acme/acme/jax/snapshotter_test.py | 139 + acme/acme/jax/types.py | 68 + acme/acme/jax/utils.py | 580 ++ acme/acme/jax/utils_test.py | 87 + acme/acme/jax/variable_utils.py | 152 + acme/acme/jax/variable_utils_test.py | 63 + acme/acme/multiagent/__init__.py | 15 + acme/acme/multiagent/types.py | 51 + acme/acme/multiagent/utils.py | 48 + acme/acme/multiagent/utils_test.py | 59 + acme/acme/specs.py | 48 + acme/acme/testing/__init__.py | 15 + acme/acme/testing/fakes.py | 474 ++ acme/acme/testing/multiagent_fakes.py | 50 + acme/acme/testing/test_utils.py | 33 + acme/acme/tf/__init__.py | 14 + acme/acme/tf/losses/__init__.py | 29 + acme/acme/tf/losses/distributional.py | 236 + acme/acme/tf/losses/distributional_test.py | 177 + acme/acme/tf/losses/dpg.py | 59 + acme/acme/tf/losses/huber.py | 56 + acme/acme/tf/losses/mompo.py | 353 ++ acme/acme/tf/losses/mpo.py | 439 ++ acme/acme/tf/losses/quantile.py | 94 + acme/acme/tf/losses/r2d2.py | 179 + acme/acme/tf/networks/__init__.py | 66 + acme/acme/tf/networks/atari.py | 191 + acme/acme/tf/networks/base.py | 66 + acme/acme/tf/networks/continuous.py | 138 + acme/acme/tf/networks/discrete.py | 45 + acme/acme/tf/networks/distributional.py | 314 + acme/acme/tf/networks/distributional_test.py | 68 + acme/acme/tf/networks/distributions.py | 107 + acme/acme/tf/networks/distributions_test.py | 67 + acme/acme/tf/networks/duelling.py | 58 + acme/acme/tf/networks/embedding.py | 45 + acme/acme/tf/networks/legal_actions.py | 127 + .../acme/tf/networks/masked_epsilon_greedy.py | 55 + acme/acme/tf/networks/multihead.py | 52 + acme/acme/tf/networks/multiplexers.py | 79 + acme/acme/tf/networks/noise.py | 40 + acme/acme/tf/networks/policy_value.py | 36 + acme/acme/tf/networks/quantile.py | 94 + acme/acme/tf/networks/recurrence.py | 375 ++ acme/acme/tf/networks/recurrence_test.py | 88 + acme/acme/tf/networks/rescaling.py | 73 + acme/acme/tf/networks/stochastic.py | 104 + acme/acme/tf/networks/vision.py | 236 + acme/acme/tf/savers.py | 460 ++ acme/acme/tf/savers_test.py | 294 + acme/acme/tf/utils.py | 184 + acme/acme/tf/utils_test.py | 134 + acme/acme/tf/variable_utils.py | 108 + acme/acme/tf/variable_utils_test.py | 133 + acme/acme/types.py | 65 + acme/acme/utils/__init__.py | 15 + acme/acme/utils/async_utils.py | 113 + acme/acme/utils/counting.py | 139 + acme/acme/utils/counting_test.py | 117 + acme/acme/utils/experiment_utils.py | 28 + acme/acme/utils/frozen_learner.py | 58 + acme/acme/utils/frozen_learner_test.py | 77 + acme/acme/utils/iterator_utils.py | 38 + acme/acme/utils/iterator_utils_test.py | 40 + acme/acme/utils/loggers/__init__.py | 38 + acme/acme/utils/loggers/aggregators.py | 42 + acme/acme/utils/loggers/asynchronous.py | 42 + acme/acme/utils/loggers/auto_close.py | 43 + acme/acme/utils/loggers/base.py | 87 + acme/acme/utils/loggers/base_test.py | 41 + acme/acme/utils/loggers/constant.py | 46 + acme/acme/utils/loggers/csv.py | 141 + acme/acme/utils/loggers/csv_test.py | 102 + acme/acme/utils/loggers/dataframe.py | 52 + acme/acme/utils/loggers/default.py | 69 + acme/acme/utils/loggers/filters.py | 165 + acme/acme/utils/loggers/filters_test.py | 112 + acme/acme/utils/loggers/image.py | 76 + acme/acme/utils/loggers/image_test.py | 60 + acme/acme/utils/loggers/terminal.py | 95 + acme/acme/utils/loggers/terminal_test.py | 46 + acme/acme/utils/loggers/tf_summary.py | 74 + acme/acme/utils/lp_utils.py | 229 + acme/acme/utils/lp_utils_test.py | 52 + acme/acme/utils/metrics.py | 23 + acme/acme/utils/observers/__init__.py | 22 + acme/acme/utils/observers/action_metrics.py | 66 + .../utils/observers/action_metrics_test.py | 127 + acme/acme/utils/observers/action_norm.py | 44 + acme/acme/utils/observers/action_norm_test.py | 57 + acme/acme/utils/observers/base.py | 42 + acme/acme/utils/observers/env_info.py | 53 + acme/acme/utils/observers/env_info_test.py | 69 + .../utils/observers/measurement_metrics.py | 74 + .../observers/measurement_metrics_test.py | 172 + acme/acme/utils/paths.py | 80 + acme/acme/utils/paths_test.py | 41 + acme/acme/utils/reverb_utils.py | 140 + acme/acme/utils/reverb_utils_test.py | 87 + acme/acme/utils/signals.py | 49 + acme/acme/utils/tree_utils.py | 191 + acme/acme/utils/tree_utils_test.py | 106 + acme/acme/wrappers/__init__.py | 37 + acme/acme/wrappers/action_repeat.py | 47 + acme/acme/wrappers/atari_wrapper.py | 390 ++ acme/acme/wrappers/atari_wrapper_dopamine.py | 67 + acme/acme/wrappers/atari_wrapper_test.py | 91 + acme/acme/wrappers/base.py | 80 + acme/acme/wrappers/base_test.py | 44 + acme/acme/wrappers/canonical_spec.py | 98 + .../acme/wrappers/concatenate_observations.py | 97 + acme/acme/wrappers/delayed_reward.py | 89 + acme/acme/wrappers/delayed_reward_test.py | 90 + .../expand_scalar_observation_shapes.py | 68 + acme/acme/wrappers/frame_stacking.py | 99 + acme/acme/wrappers/frame_stacking_test.py | 81 + acme/acme/wrappers/gym_wrapper.py | 206 + acme/acme/wrappers/gym_wrapper_test.py | 140 + acme/acme/wrappers/mujoco.py | 50 + .../wrappers/multiagent_dict_key_wrapper.py | 87 + acme/acme/wrappers/multigrid_wrapper.py | 314 + acme/acme/wrappers/noop_starts.py | 68 + acme/acme/wrappers/noop_starts_test.py | 68 + .../wrappers/observation_action_reward.py | 62 + acme/acme/wrappers/open_spiel_wrapper.py | 147 + acme/acme/wrappers/open_spiel_wrapper_test.py | 68 + acme/acme/wrappers/single_precision.py | 85 + acme/acme/wrappers/single_precision_test.py | 71 + acme/acme/wrappers/step_limit.py | 42 + acme/acme/wrappers/video.py | 237 + acme/docs/_static/custom.css | 7 + acme/docs/conf.py | 41 + acme/docs/faq.md | 42 + acme/docs/imgs/acme-notext.png | Bin 0 -> 17109 bytes acme/docs/imgs/acme.png | Bin 0 -> 21024 bytes acme/docs/index.rst | 26 + acme/docs/requirements.txt | 3 + acme/docs/user/agents.md | 115 + acme/docs/user/components.md | 366 ++ acme/docs/user/diagrams/actor_loop.png | Bin 0 -> 231106 bytes acme/docs/user/diagrams/agent_loop.png | Bin 0 -> 345217 bytes acme/docs/user/diagrams/batch_loop.png | Bin 0 -> 99875 bytes acme/docs/user/diagrams/distributed_loop.png | Bin 0 -> 350045 bytes acme/docs/user/diagrams/environment_loop.png | Bin 0 -> 187885 bytes acme/docs/user/logos/jax-small.png | Bin 0 -> 4841 bytes acme/docs/user/logos/tf-small.png | Bin 0 -> 2150 bytes acme/docs/user/overview.md | 81 + acme/examples/README.md | 83 + .../baselines/rl_continuous/helpers.py | 57 + .../baselines/rl_continuous/run_d4pg.py | 85 + .../baselines/rl_continuous/run_ppo.py | 68 + .../baselines/rl_continuous/run_sac.py | 86 + .../baselines/rl_continuous/run_td3.py | 76 + .../examples/baselines/rl_discrete/helpers.py | 118 + .../examples/baselines/rl_discrete/run_dqn.py | 91 + .../baselines/rl_discrete/run_mdqn.py | 92 + acme/examples/bsuite/run_dqn.py | 60 + acme/examples/bsuite/run_impala.py | 69 + acme/examples/bsuite/run_mcts.py | 105 + acme/examples/multiagent/multigrid/helpers.py | 208 + .../multiagent/multigrid/run_multigrid.py | 97 + acme/examples/offline/bc_utils.py | 206 + acme/examples/offline/run_bc.py | 195 + acme/examples/offline/run_bc_jax.py | 98 + acme/examples/offline/run_bcq.py | 144 + acme/examples/offline/run_cql_jax.py | 114 + acme/examples/offline/run_crr_jax.py | 137 + acme/examples/offline/run_dqfd.py | 83 + acme/examples/offline/run_mbop_jax.py | 135 + acme/examples/offline/run_offline_td3_jax.py | 144 + acme/examples/open_spiel/run_dqn.py | 75 + acme/examples/quickstart.ipynb | 468 ++ acme/examples/tf/control_suite/helpers.py | 57 + acme/examples/tf/control_suite/lp_d4pg.py | 93 + acme/examples/tf/control_suite/lp_ddpg.py | 90 + acme/examples/tf/control_suite/lp_dmpo.py | 96 + .../tf/control_suite/lp_dmpo_pixels.py | 105 + .../tf/control_suite/lp_dmpo_pixels_drqv2.py | 125 + acme/examples/tf/control_suite/lp_mpo.py | 93 + acme/examples/tutorial.ipynb | 896 +++ acme/setup.py | 177 + acme/test.sh | 86 + aco/DeepSwarm/.gitignore | 18 + aco/DeepSwarm/LICENSE | 21 + aco/DeepSwarm/Makefile | 14 + aco/DeepSwarm/README.md | 117 + aco/DeepSwarm/deepswarm/__init__.py | 69 + aco/DeepSwarm/deepswarm/aco.py | 470 ++ aco/DeepSwarm/deepswarm/backends.py | 1081 ++++ aco/DeepSwarm/deepswarm/deepswarm.py | 97 + aco/DeepSwarm/deepswarm/log.py | 111 + aco/DeepSwarm/deepswarm/nodes.py | 127 + aco/DeepSwarm/deepswarm/storage.py | 226 + aco/DeepSwarm/examples/cifar10.py | 40 + aco/DeepSwarm/examples/context.py | 7 + aco/DeepSwarm/examples/fashion-mnist.py | 36 + aco/DeepSwarm/examples/mnist.py | 36 + aco/DeepSwarm/requirements.txt | 6 + aco/DeepSwarm/setup.py | 27 + aco/DeepSwarm/tests/test_aco.py | 58 + aco/DeepSwarm/tests/test_graph.py | 77 + aco/DeepSwarm/tests/test_nodes.py | 93 + aco/DeepSwarm/train.py | 16 + activate_arch_gym.sh | 6 + bo/CustomBayesOpt.py | 40 + bo/CustomEstimator.py | 51 + bo/CustomGridSearch.py | 27 + bo/DRAMSysEstimator.py | 205 + bo/FARSIEnvEstimator.py | 357 ++ bo/MaestroEstimator.py | 227 + bo/SniperEstimator.py | 162 + bo/TimeloopEstimator.py | 278 + configs/configs_random_env.py | 3 + data/proxy_model_converter.py | 60 + docs/ArchGym_Framework_Overview.png | Bin 0 -> 147407 bytes docs/ArchGym_Overview.pdf | Bin 0 -> 64508 bytes environment.yml | 24 + settings/default_dramsys.yaml | 96 + settings/default_farsi_limited.yaml | 72 + settings/default_maestro.yaml | 228 + settings/default_sniper.yaml | 96 + settings/default_timeloop.yaml | 64 + sko/ACA.py | 72 + sko/AFSA.py | 210 + sko/DE.py | 95 + sko/GA.py | 454 ++ sko/IA.py | 40 + sko/PSO.py | 203 + sko/SA.py | 229 + sko/__init__.py | 14 + sko/base.py | 30 + sko/demo_func.py | 145 + sko/operators/__init__.py | 0 sko/operators/crossover.py | 112 + sko/operators/mutation.py | 81 + sko/operators/ranking.py | 20 + sko/operators/selection.py | 65 + sko/operators_gpu/__init__.py | 0 sko/operators_gpu/crossover_gpu.py | 18 + sko/operators_gpu/mutation_gpu.py | 13 + sko/operators_gpu/ranking_gpu.py | 0 sko/operators_gpu/selection_gpu.py | 18 + sko/requirements.txt | 6 + sko/tool_kit.py | 21 + sko/tools.py | 126 + tests/custom-env/random_simpleEnv.py | 27 + tests/custom-env/train_simpleEnv.py | 0 tests/rllib/ppo_test.py | 24 + tests/rllib/ppo_train.py | 39 + 860 files changed, 134215 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Project_FARSI/.gitignore create mode 100644 Project_FARSI/BUCKCONFIG_FILE_EXISTS.md create mode 100644 Project_FARSI/CODE_OF_CONDUCT.md create mode 100644 Project_FARSI/CONTRIBUTING.md create mode 100644 Project_FARSI/DOCUSAURUS_ENABLED.md create mode 100644 Project_FARSI/DSE_utils/__init__.py create mode 100644 Project_FARSI/DSE_utils/design_space_exploration_handler.py create mode 100644 Project_FARSI/DSE_utils/exhaustive_DSE.py create mode 100644 Project_FARSI/DSE_utils/hill_climbing.py create mode 100644 Project_FARSI/DSE_utils/simple_minimal_change_ga.py create mode 100644 Project_FARSI/EXTERNAL_CONTINUOUS_INTEGRATION_RUNS.md create mode 100644 Project_FARSI/GHANGELOG.md create mode 100644 Project_FARSI/GITHUB_ISSUE_TEMPLATE_EXISTS.md create mode 100644 Project_FARSI/GROUP_NOTIFICATIONS.md create mode 100644 Project_FARSI/LICENSE create mode 100644 Project_FARSI/PAGES_ENABLED.md create mode 100644 Project_FARSI/README.md create mode 100644 Project_FARSI/SHIP_IT_IS_ENABLED.md create mode 100644 Project_FARSI/SHIP_IT_IS_SET_UP.md create mode 100644 Project_FARSI/SIM_utils/SIM.py create mode 100644 Project_FARSI/SIM_utils/__init__.py create mode 100644 Project_FARSI/SIM_utils/components/__init__.py create mode 100644 Project_FARSI/SIM_utils/components/perf_sim.py create mode 100644 Project_FARSI/SIM_utils/components/pow_sim.py create mode 100644 Project_FARSI/__init__.py create mode 100644 Project_FARSI/cacti_for_FARSI/2DDRAM_Samsung2GbDDR2.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/2DDRAM_micron1Gb.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/3DDRAM_Samsung3D8Gb_extened.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/README create mode 100644 Project_FARSI/cacti_for_FARSI/TSV.cc create mode 100644 Project_FARSI/cacti_for_FARSI/TSV.h create mode 100644 Project_FARSI/cacti_for_FARSI/Ucache.cc create mode 100644 Project_FARSI/cacti_for_FARSI/Ucache.h create mode 100644 Project_FARSI/cacti_for_FARSI/arbiter.cc create mode 100644 Project_FARSI/cacti_for_FARSI/arbiter.h create mode 100644 Project_FARSI/cacti_for_FARSI/area.cc create mode 100644 Project_FARSI/cacti_for_FARSI/area.h create mode 100644 Project_FARSI/cacti_for_FARSI/bank.cc create mode 100644 Project_FARSI/cacti_for_FARSI/bank.h create mode 100644 Project_FARSI/cacti_for_FARSI/basic_circuit.cc create mode 100644 Project_FARSI/cacti_for_FARSI/basic_circuit.h create mode 100644 Project_FARSI/cacti_for_FARSI/cache.cfg create mode 100755 Project_FARSI/cacti_for_FARSI/cacti create mode 100644 Project_FARSI/cacti_for_FARSI/cacti.i create mode 100644 Project_FARSI/cacti_for_FARSI/cacti.mk create mode 100644 Project_FARSI/cacti_for_FARSI/cacti_interface.cc create mode 100644 Project_FARSI/cacti_for_FARSI/cacti_interface.h create mode 100644 Project_FARSI/cacti_for_FARSI/component.cc create mode 100644 Project_FARSI/cacti_for_FARSI/component.h create mode 100644 Project_FARSI/cacti_for_FARSI/const.h create mode 100644 Project_FARSI/cacti_for_FARSI/contention.dat create mode 100644 Project_FARSI/cacti_for_FARSI/crossbar.cc create mode 100644 Project_FARSI/cacti_for_FARSI/crossbar.h create mode 100644 Project_FARSI/cacti_for_FARSI/ddr3.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/decoder.cc create mode 100644 Project_FARSI/cacti_for_FARSI/decoder.h create mode 100644 Project_FARSI/cacti_for_FARSI/dram.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/extio.cc create mode 100644 Project_FARSI/cacti_for_FARSI/extio.h create mode 100644 Project_FARSI/cacti_for_FARSI/extio_technology.cc create mode 100644 Project_FARSI/cacti_for_FARSI/extio_technology.h create mode 100644 Project_FARSI/cacti_for_FARSI/farsi_gen.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/htree2.cc create mode 100644 Project_FARSI/cacti_for_FARSI/htree2.h create mode 100644 Project_FARSI/cacti_for_FARSI/io.cc create mode 100644 Project_FARSI/cacti_for_FARSI/io.h create mode 100644 Project_FARSI/cacti_for_FARSI/lpddr.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/main.cc create mode 100644 Project_FARSI/cacti_for_FARSI/makefile create mode 100644 Project_FARSI/cacti_for_FARSI/mat.cc create mode 100644 Project_FARSI/cacti_for_FARSI/mat.h create mode 100644 Project_FARSI/cacti_for_FARSI/memcad.cc create mode 100644 Project_FARSI/cacti_for_FARSI/memcad.h create mode 100644 Project_FARSI/cacti_for_FARSI/memcad_parameters.cc create mode 100644 Project_FARSI/cacti_for_FARSI/memcad_parameters.h create mode 100644 Project_FARSI/cacti_for_FARSI/memorybus.cc create mode 100644 Project_FARSI/cacti_for_FARSI/memorybus.h create mode 100644 Project_FARSI/cacti_for_FARSI/nuca.cc create mode 100644 Project_FARSI/cacti_for_FARSI/nuca.h create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/TSV.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/Ucache.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/arbiter.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/area.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/bank.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/basic_circuit.o create mode 100755 Project_FARSI/cacti_for_FARSI/obj_dbg/cacti create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/cacti_interface.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/component.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/crossbar.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/decoder.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/extio.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/extio_technology.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/htree2.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/io.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/main.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/mat.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/memcad.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/memcad_parameters.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/memorybus.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/nuca.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/parameter.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/powergating.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/router.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/subarray.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/technology.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/uca.o create mode 100644 Project_FARSI/cacti_for_FARSI/obj_dbg/wire.o create mode 100644 Project_FARSI/cacti_for_FARSI/parameter.cc create mode 100644 Project_FARSI/cacti_for_FARSI/parameter.h create mode 100644 Project_FARSI/cacti_for_FARSI/powergating.cc create mode 100644 Project_FARSI/cacti_for_FARSI/powergating.h create mode 100755 Project_FARSI/cacti_for_FARSI/regression.test create mode 100644 Project_FARSI/cacti_for_FARSI/router.cc create mode 100644 Project_FARSI/cacti_for_FARSI/router.h create mode 100644 Project_FARSI/cacti_for_FARSI/sample_config_files/ddr3_cache.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/sample_config_files/diff_ddr3_cache.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/sample_config_files/lpddr3_cache.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/sample_config_files/wideio_cache.cfg create mode 100644 Project_FARSI/cacti_for_FARSI/subarray.cc create mode 100644 Project_FARSI/cacti_for_FARSI/subarray.h create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/16nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/180nm-old.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/180nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/22nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/32nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/45nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/65nm-old.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/65nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/90nm-old.dat create mode 100644 Project_FARSI/cacti_for_FARSI/tech_params/90nm.dat create mode 100644 Project_FARSI/cacti_for_FARSI/technology.cc create mode 100644 Project_FARSI/cacti_for_FARSI/uca.cc create mode 100644 Project_FARSI/cacti_for_FARSI/uca.h create mode 100644 Project_FARSI/cacti_for_FARSI/version_cacti.h create mode 100644 Project_FARSI/cacti_for_FARSI/wire.cc create mode 100644 Project_FARSI/cacti_for_FARSI/wire.h create mode 100644 Project_FARSI/data_collection/collection_utils/home_settings.py create mode 100644 Project_FARSI/data_collection/collection_utils/replay/FARSI_replay.py create mode 100644 Project_FARSI/data_collection/collection_utils/replay/replayer.py create mode 100644 Project_FARSI/data_collection/collection_utils/sim_run/exploration_time.png create mode 100644 Project_FARSI/data_collection/collection_utils/sim_run/sim_run_array_x.py create mode 100644 Project_FARSI/data_collection/collection_utils/sim_run/simple_sim_run.py create mode 100644 Project_FARSI/data_collection/collection_utils/what_ifs/FARSI_what_ifs.py create mode 100644 Project_FARSI/data_collection/collection_utils/what_ifs/FARSI_what_ifs_simple.py create mode 100644 Project_FARSI/data_collection/collection_utils/what_ifs/FARSI_what_ifs_with_params.py create mode 100644 Project_FARSI/data_collection/collection_utils/what_ifs/autoWLandDep.py create mode 100644 Project_FARSI/design_utils/__init__.py create mode 100644 Project_FARSI/design_utils/common_design_utils.py create mode 100644 Project_FARSI/design_utils/components/__init__.py create mode 100644 Project_FARSI/design_utils/components/hardware.py create mode 100644 Project_FARSI/design_utils/components/krnel.py create mode 100644 Project_FARSI/design_utils/components/mapping.py create mode 100644 Project_FARSI/design_utils/components/scheduling.py create mode 100644 Project_FARSI/design_utils/components/workload.py create mode 100644 Project_FARSI/design_utils/des_handler.py create mode 100644 Project_FARSI/design_utils/design.py create mode 100644 Project_FARSI/error_handling/custom_error.py create mode 100644 Project_FARSI/figures/DSE_on_the_map.pdf create mode 100644 Project_FARSI/figures/DSE_on_the_map.png create mode 100644 Project_FARSI/figures/FARSI_methodology.pdf create mode 100644 Project_FARSI/figures/FARSI_methodology.png create mode 100644 Project_FARSI/figures/FARSI_output.pdf create mode 100644 Project_FARSI/figures/FARSI_output.png create mode 100644 Project_FARSI/misc/__init__.py create mode 100644 Project_FARSI/misc/cacti_hndlr/cact_handlr.py create mode 100644 Project_FARSI/misc/cacti_hndlr/ddr3_.cfg create mode 100644 Project_FARSI/misc/converters/dot_to_png.py create mode 100644 Project_FARSI/misc/gotchas create mode 100644 Project_FARSI/misc/scratch/__init__.py create mode 100644 Project_FARSI/readme create mode 100644 Project_FARSI/settings/__init__.py create mode 100644 Project_FARSI/settings/config.py create mode 100644 Project_FARSI/settings/config_cacti.py create mode 100644 Project_FARSI/settings/config_plotting.py create mode 100644 Project_FARSI/specs/LW_cl.py create mode 100644 Project_FARSI/specs/__init__.py create mode 100644 Project_FARSI/specs/data_base.py create mode 100644 Project_FARSI/specs/database_data/generate/input.py create mode 100644 Project_FARSI/specs/database_data/hardcoded_test.txt create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Budget.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Hardware Graph.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task Data Movement.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task Itr Count.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task PE Area.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task PE Energy.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task PE Performance.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_2r_database - Task to Hardware Mapping.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Budget.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Hardware Graph.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task Data Movement.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task Itr Count.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task PE Area.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task PE Energy.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task PE Performance.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_1p_database - Task To Hardware Mapping.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Budget.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Hardware Graph.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task Data Movement.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task Itr Count.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task PE Area.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task PE Energy.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task PE Performance.csv create mode 100755 Project_FARSI/specs/database_data/parsing/SOC_example_8p_database - Task To Hardware Mapping.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Budget.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Hardware Graph.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task PE Performance for 1000 blocks.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/audio_decoder_database - misc.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_1_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_1_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_1_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_1_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_1_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_2_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_2_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_2_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_2_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_2_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_3_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_3_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_3_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_3_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_3_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_4_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_4_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_4_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_4_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_4_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/edge_detection_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Hardware Graph.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task Data Movement.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task Itr Count.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task PE Area.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task PE Energy.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task PE Performance.csv create mode 100644 Project_FARSI/specs/database_data/parsing/hpvm_cava_database - Task To Hardware Mapping.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Block Characteristics.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Block Characteristics_for_PA.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Block Characteristics_for_real.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Budget.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Budget_for_PA.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Budget_for_real.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Budget_individiaul.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Budget_individually.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Common Hardware.csv create mode 100644 Project_FARSI/specs/database_data/parsing/misc_database - Last Tasks.csv create mode 100644 Project_FARSI/specs/database_input.py create mode 100644 Project_FARSI/specs/gen_synthetic_data.py create mode 100644 Project_FARSI/specs/parse_libraries/__init__.py create mode 100644 Project_FARSI/specs/parse_libraries/parse_library.py create mode 100644 Project_FARSI/top/main_FARSI.py create mode 100644 Project_FARSI/visualization_utils/Iulian_plots/error_analysis_per_app.py create mode 100644 Project_FARSI/visualization_utils/Iulian_plots/plot_err_vs_arch.py create mode 100644 Project_FARSI/visualization_utils/Iulian_plots/plot_sim_vs_lat.py create mode 100644 Project_FARSI/visualization_utils/Iulian_plots/plot_validations.py create mode 100644 Project_FARSI/visualization_utils/Iulian_plots/simtime_vs_lat.py create mode 100644 Project_FARSI/visualization_utils/__init__.py create mode 100644 Project_FARSI/visualization_utils/plot.py create mode 100644 Project_FARSI/visualization_utils/plot_arrows.py create mode 100644 Project_FARSI/visualization_utils/plotting-ying.py create mode 100644 Project_FARSI/visualization_utils/plotting.py create mode 100644 Project_FARSI/visualization_utils/plotting_Iulian.py create mode 100644 Project_FARSI/visualization_utils/vis_hardware.py create mode 100644 Project_FARSI/visualization_utils/vis_sim.py create mode 100644 Project_FARSI/visualization_utils/vis_stats.py create mode 100644 acme/.gitignore create mode 100644 acme/.pylintrc create mode 100644 acme/.readthedocs.yaml create mode 100644 acme/CONTRIBUTING.md create mode 100644 acme/LICENSE create mode 100644 acme/MANIFEST.in create mode 100644 acme/README.md create mode 100644 acme/acme/__init__.py create mode 100644 acme/acme/_metadata.py create mode 100644 acme/acme/adders/__init__.py create mode 100644 acme/acme/adders/base.py create mode 100644 acme/acme/adders/reverb/__init__.py create mode 100644 acme/acme/adders/reverb/base.py create mode 100644 acme/acme/adders/reverb/episode.py create mode 100644 acme/acme/adders/reverb/episode_test.py create mode 100644 acme/acme/adders/reverb/sequence.py create mode 100644 acme/acme/adders/reverb/sequence_test.py create mode 100644 acme/acme/adders/reverb/structured.py create mode 100644 acme/acme/adders/reverb/structured_test.py create mode 100644 acme/acme/adders/reverb/test_cases.py create mode 100644 acme/acme/adders/reverb/test_utils.py create mode 100644 acme/acme/adders/reverb/transition.py create mode 100644 acme/acme/adders/reverb/transition_test.py create mode 100644 acme/acme/adders/reverb/utils.py create mode 100644 acme/acme/adders/wrappers.py create mode 100644 acme/acme/agents/__init__.py create mode 100644 acme/acme/agents/agent.py create mode 100644 acme/acme/agents/jax/__init__.py create mode 100644 acme/acme/agents/jax/actor_core.py create mode 100644 acme/acme/agents/jax/actors.py create mode 100644 acme/acme/agents/jax/actors_test.py create mode 100644 acme/acme/agents/jax/ail/README.md create mode 100644 acme/acme/agents/jax/ail/__init__.py create mode 100644 acme/acme/agents/jax/ail/builder.py create mode 100644 acme/acme/agents/jax/ail/builder_test.py create mode 100644 acme/acme/agents/jax/ail/config.py create mode 100644 acme/acme/agents/jax/ail/dac.py create mode 100644 acme/acme/agents/jax/ail/gail.py create mode 100644 acme/acme/agents/jax/ail/learning.py create mode 100644 acme/acme/agents/jax/ail/learning_test.py create mode 100644 acme/acme/agents/jax/ail/losses.py create mode 100644 acme/acme/agents/jax/ail/losses_test.py create mode 100644 acme/acme/agents/jax/ail/networks.py create mode 100644 acme/acme/agents/jax/ail/rewards.py create mode 100644 acme/acme/agents/jax/ars/README.md create mode 100644 acme/acme/agents/jax/ars/__init__.py create mode 100644 acme/acme/agents/jax/ars/builder.py create mode 100644 acme/acme/agents/jax/ars/config.py create mode 100644 acme/acme/agents/jax/ars/learning.py create mode 100644 acme/acme/agents/jax/ars/networks.py create mode 100644 acme/acme/agents/jax/bc/README.md create mode 100644 acme/acme/agents/jax/bc/__init__.py create mode 100644 acme/acme/agents/jax/bc/agent_test.py create mode 100644 acme/acme/agents/jax/bc/builder.py create mode 100644 acme/acme/agents/jax/bc/config.py create mode 100644 acme/acme/agents/jax/bc/learning.py create mode 100644 acme/acme/agents/jax/bc/losses.py create mode 100644 acme/acme/agents/jax/bc/networks.py create mode 100644 acme/acme/agents/jax/bc/pretraining.py create mode 100644 acme/acme/agents/jax/bc/pretraining_test.py create mode 100644 acme/acme/agents/jax/builders.py create mode 100644 acme/acme/agents/jax/cql/README.md create mode 100644 acme/acme/agents/jax/cql/__init__.py create mode 100644 acme/acme/agents/jax/cql/agent_test.py create mode 100644 acme/acme/agents/jax/cql/builder.py create mode 100644 acme/acme/agents/jax/cql/config.py create mode 100644 acme/acme/agents/jax/cql/learning.py create mode 100644 acme/acme/agents/jax/cql/networks.py create mode 100644 acme/acme/agents/jax/crr/README.md create mode 100644 acme/acme/agents/jax/crr/__init__.py create mode 100644 acme/acme/agents/jax/crr/agent_test.py create mode 100644 acme/acme/agents/jax/crr/builder.py create mode 100644 acme/acme/agents/jax/crr/config.py create mode 100644 acme/acme/agents/jax/crr/learning.py create mode 100644 acme/acme/agents/jax/crr/losses.py create mode 100644 acme/acme/agents/jax/crr/networks.py create mode 100644 acme/acme/agents/jax/d4pg/README.md create mode 100644 acme/acme/agents/jax/d4pg/__init__.py create mode 100644 acme/acme/agents/jax/d4pg/builder.py create mode 100644 acme/acme/agents/jax/d4pg/config.py create mode 100644 acme/acme/agents/jax/d4pg/learning.py create mode 100644 acme/acme/agents/jax/d4pg/networks.py create mode 100644 acme/acme/agents/jax/dqn/README.md create mode 100644 acme/acme/agents/jax/dqn/__init__.py create mode 100644 acme/acme/agents/jax/dqn/actor.py create mode 100644 acme/acme/agents/jax/dqn/builder.py create mode 100644 acme/acme/agents/jax/dqn/config.py create mode 100644 acme/acme/agents/jax/dqn/learning.py create mode 100644 acme/acme/agents/jax/dqn/learning_lib.py create mode 100644 acme/acme/agents/jax/dqn/losses.py create mode 100644 acme/acme/agents/jax/dqn/rainbow.py create mode 100644 acme/acme/agents/jax/impala/__init__.py create mode 100644 acme/acme/agents/jax/impala/acting.py create mode 100644 acme/acme/agents/jax/impala/builder.py create mode 100644 acme/acme/agents/jax/impala/config.py create mode 100644 acme/acme/agents/jax/impala/learning.py create mode 100644 acme/acme/agents/jax/impala/networks.py create mode 100644 acme/acme/agents/jax/impala/types.py create mode 100644 acme/acme/agents/jax/lfd/__init__.py create mode 100644 acme/acme/agents/jax/lfd/builder.py create mode 100644 acme/acme/agents/jax/lfd/config.py create mode 100644 acme/acme/agents/jax/lfd/lfd_adder.py create mode 100644 acme/acme/agents/jax/lfd/lfd_adder_test.py create mode 100644 acme/acme/agents/jax/lfd/sacfd.py create mode 100644 acme/acme/agents/jax/lfd/td3fd.py create mode 100644 acme/acme/agents/jax/mbop/README.md create mode 100644 acme/acme/agents/jax/mbop/__init__.py create mode 100644 acme/acme/agents/jax/mbop/acting.py create mode 100644 acme/acme/agents/jax/mbop/agent_test.py create mode 100644 acme/acme/agents/jax/mbop/dataset.py create mode 100644 acme/acme/agents/jax/mbop/dataset_test.py create mode 100644 acme/acme/agents/jax/mbop/ensemble.py create mode 100644 acme/acme/agents/jax/mbop/ensemble_test.py create mode 100644 acme/acme/agents/jax/mbop/learning.py create mode 100644 acme/acme/agents/jax/mbop/losses.py create mode 100644 acme/acme/agents/jax/mbop/models.py create mode 100644 acme/acme/agents/jax/mbop/mppi.py create mode 100644 acme/acme/agents/jax/mbop/mppi_test.py create mode 100644 acme/acme/agents/jax/mbop/networks.py create mode 100644 acme/acme/agents/jax/multiagent/__init__.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/README.md create mode 100644 acme/acme/agents/jax/multiagent/decentralized/__init__.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/actor.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/agents.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/agents_test.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/builder.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/config.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/factories.py create mode 100644 acme/acme/agents/jax/multiagent/decentralized/learner_set.py create mode 100644 acme/acme/agents/jax/normalization.py create mode 100644 acme/acme/agents/jax/ppo/README.md create mode 100644 acme/acme/agents/jax/ppo/__init__.py create mode 100644 acme/acme/agents/jax/ppo/builder.py create mode 100644 acme/acme/agents/jax/ppo/config.py create mode 100644 acme/acme/agents/jax/ppo/learning.py create mode 100644 acme/acme/agents/jax/ppo/networks.py create mode 100644 acme/acme/agents/jax/pwil/README.md create mode 100644 acme/acme/agents/jax/pwil/__init__.py create mode 100644 acme/acme/agents/jax/pwil/adder.py create mode 100644 acme/acme/agents/jax/pwil/builder.py create mode 100644 acme/acme/agents/jax/pwil/config.py create mode 100644 acme/acme/agents/jax/pwil/rewarder.py create mode 100644 acme/acme/agents/jax/r2d2/__init__.py create mode 100644 acme/acme/agents/jax/r2d2/actor.py create mode 100644 acme/acme/agents/jax/r2d2/builder.py create mode 100644 acme/acme/agents/jax/r2d2/config.py create mode 100644 acme/acme/agents/jax/r2d2/learning.py create mode 100644 acme/acme/agents/jax/r2d2/networks.py create mode 100644 acme/acme/agents/jax/rnd/README.md create mode 100644 acme/acme/agents/jax/rnd/__init__.py create mode 100644 acme/acme/agents/jax/rnd/builder.py create mode 100644 acme/acme/agents/jax/rnd/config.py create mode 100644 acme/acme/agents/jax/rnd/learning.py create mode 100644 acme/acme/agents/jax/rnd/networks.py create mode 100644 acme/acme/agents/jax/sac/README.md create mode 100644 acme/acme/agents/jax/sac/__init__.py create mode 100644 acme/acme/agents/jax/sac/builder.py create mode 100644 acme/acme/agents/jax/sac/config.py create mode 100644 acme/acme/agents/jax/sac/learning.py create mode 100644 acme/acme/agents/jax/sac/networks.py create mode 100644 acme/acme/agents/jax/sqil/README.md create mode 100644 acme/acme/agents/jax/sqil/__init__.py create mode 100644 acme/acme/agents/jax/sqil/builder.py create mode 100644 acme/acme/agents/jax/sqil/builder_test.py create mode 100644 acme/acme/agents/jax/td3/README.md create mode 100644 acme/acme/agents/jax/td3/__init__.py create mode 100644 acme/acme/agents/jax/td3/builder.py create mode 100644 acme/acme/agents/jax/td3/config.py create mode 100644 acme/acme/agents/jax/td3/learning.py create mode 100644 acme/acme/agents/jax/td3/networks.py create mode 100644 acme/acme/agents/jax/value_dice/README.md create mode 100644 acme/acme/agents/jax/value_dice/__init__.py create mode 100644 acme/acme/agents/jax/value_dice/builder.py create mode 100644 acme/acme/agents/jax/value_dice/config.py create mode 100644 acme/acme/agents/jax/value_dice/learning.py create mode 100644 acme/acme/agents/jax/value_dice/networks.py create mode 100644 acme/acme/agents/replay.py create mode 100644 acme/acme/agents/tf/__init__.py create mode 100644 acme/acme/agents/tf/actors.py create mode 100644 acme/acme/agents/tf/actors_test.py create mode 100644 acme/acme/agents/tf/bc/README.md create mode 100644 acme/acme/agents/tf/bc/__init__.py create mode 100644 acme/acme/agents/tf/bc/learning.py create mode 100644 acme/acme/agents/tf/bcq/README.md create mode 100644 acme/acme/agents/tf/bcq/__init__.py create mode 100644 acme/acme/agents/tf/bcq/discrete_learning.py create mode 100644 acme/acme/agents/tf/bcq/discrete_learning_test.py create mode 100644 acme/acme/agents/tf/crr/__init__.py create mode 100644 acme/acme/agents/tf/crr/recurrent_learning.py create mode 100644 acme/acme/agents/tf/d4pg/README.md create mode 100644 acme/acme/agents/tf/d4pg/__init__.py create mode 100644 acme/acme/agents/tf/d4pg/agent.py create mode 100644 acme/acme/agents/tf/d4pg/agent_distributed.py create mode 100644 acme/acme/agents/tf/d4pg/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/d4pg/agent_test.py create mode 100644 acme/acme/agents/tf/d4pg/learning.py create mode 100644 acme/acme/agents/tf/d4pg/networks.py create mode 100644 acme/acme/agents/tf/ddpg/README.md create mode 100644 acme/acme/agents/tf/ddpg/__init__.py create mode 100644 acme/acme/agents/tf/ddpg/agent.py create mode 100644 acme/acme/agents/tf/ddpg/agent_distributed.py create mode 100644 acme/acme/agents/tf/ddpg/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/ddpg/agent_test.py create mode 100644 acme/acme/agents/tf/ddpg/learning.py create mode 100644 acme/acme/agents/tf/dmpo/README.md create mode 100644 acme/acme/agents/tf/dmpo/__init__.py create mode 100644 acme/acme/agents/tf/dmpo/agent.py create mode 100644 acme/acme/agents/tf/dmpo/agent_distributed.py create mode 100644 acme/acme/agents/tf/dmpo/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/dmpo/agent_test.py create mode 100644 acme/acme/agents/tf/dmpo/learning.py create mode 100644 acme/acme/agents/tf/dqfd/README.md create mode 100644 acme/acme/agents/tf/dqfd/__init__.py create mode 100644 acme/acme/agents/tf/dqfd/agent.py create mode 100644 acme/acme/agents/tf/dqfd/agent_test.py create mode 100644 acme/acme/agents/tf/dqfd/bsuite_demonstrations.py create mode 100644 acme/acme/agents/tf/dqn/README.md create mode 100644 acme/acme/agents/tf/dqn/__init__.py create mode 100644 acme/acme/agents/tf/dqn/agent.py create mode 100644 acme/acme/agents/tf/dqn/agent_distributed.py create mode 100644 acme/acme/agents/tf/dqn/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/dqn/agent_test.py create mode 100644 acme/acme/agents/tf/dqn/learning.py create mode 100644 acme/acme/agents/tf/impala/README.md create mode 100644 acme/acme/agents/tf/impala/__init__.py create mode 100644 acme/acme/agents/tf/impala/acting.py create mode 100644 acme/acme/agents/tf/impala/agent.py create mode 100644 acme/acme/agents/tf/impala/agent_distributed.py create mode 100644 acme/acme/agents/tf/impala/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/impala/agent_test.py create mode 100644 acme/acme/agents/tf/impala/learning.py create mode 100644 acme/acme/agents/tf/iqn/README.md create mode 100644 acme/acme/agents/tf/iqn/__init__.py create mode 100644 acme/acme/agents/tf/iqn/learning.py create mode 100644 acme/acme/agents/tf/iqn/learning_test.py create mode 100644 acme/acme/agents/tf/mcts/README.md create mode 100644 acme/acme/agents/tf/mcts/__init__.py create mode 100644 acme/acme/agents/tf/mcts/acting.py create mode 100644 acme/acme/agents/tf/mcts/agent.py create mode 100644 acme/acme/agents/tf/mcts/agent_distributed.py create mode 100644 acme/acme/agents/tf/mcts/agent_test.py create mode 100644 acme/acme/agents/tf/mcts/learning.py create mode 100644 acme/acme/agents/tf/mcts/models/__init__.py create mode 100644 acme/acme/agents/tf/mcts/models/base.py create mode 100644 acme/acme/agents/tf/mcts/models/mlp.py create mode 100644 acme/acme/agents/tf/mcts/models/simulator.py create mode 100644 acme/acme/agents/tf/mcts/models/simulator_test.py create mode 100644 acme/acme/agents/tf/mcts/search.py create mode 100644 acme/acme/agents/tf/mcts/search_test.py create mode 100644 acme/acme/agents/tf/mcts/types.py create mode 100644 acme/acme/agents/tf/mog_mpo/README.md create mode 100644 acme/acme/agents/tf/mog_mpo/__init__.py create mode 100644 acme/acme/agents/tf/mog_mpo/agent_distributed.py create mode 100644 acme/acme/agents/tf/mog_mpo/learning.py create mode 100644 acme/acme/agents/tf/mog_mpo/networks.py create mode 100644 acme/acme/agents/tf/mompo/README.md create mode 100644 acme/acme/agents/tf/mompo/__init__.py create mode 100644 acme/acme/agents/tf/mompo/agent.py create mode 100644 acme/acme/agents/tf/mompo/agent_distributed.py create mode 100644 acme/acme/agents/tf/mompo/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/mompo/agent_test.py create mode 100644 acme/acme/agents/tf/mompo/learning.py create mode 100644 acme/acme/agents/tf/mpo/README.md create mode 100644 acme/acme/agents/tf/mpo/__init__.py create mode 100644 acme/acme/agents/tf/mpo/agent.py create mode 100644 acme/acme/agents/tf/mpo/agent_distributed.py create mode 100644 acme/acme/agents/tf/mpo/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/mpo/agent_test.py create mode 100644 acme/acme/agents/tf/mpo/learning.py create mode 100644 acme/acme/agents/tf/r2d2/README.md create mode 100644 acme/acme/agents/tf/r2d2/__init__.py create mode 100644 acme/acme/agents/tf/r2d2/agent.py create mode 100644 acme/acme/agents/tf/r2d2/agent_distributed.py create mode 100644 acme/acme/agents/tf/r2d2/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/r2d2/agent_test.py create mode 100644 acme/acme/agents/tf/r2d2/learning.py create mode 100644 acme/acme/agents/tf/r2d3/README.md create mode 100644 acme/acme/agents/tf/r2d3/__init__.py create mode 100644 acme/acme/agents/tf/r2d3/agent.py create mode 100644 acme/acme/agents/tf/r2d3/agent_test.py create mode 100644 acme/acme/agents/tf/svg0_prior/README.md create mode 100644 acme/acme/agents/tf/svg0_prior/__init__.py create mode 100644 acme/acme/agents/tf/svg0_prior/acting.py create mode 100644 acme/acme/agents/tf/svg0_prior/agent.py create mode 100644 acme/acme/agents/tf/svg0_prior/agent_distributed.py create mode 100644 acme/acme/agents/tf/svg0_prior/agent_distributed_test.py create mode 100644 acme/acme/agents/tf/svg0_prior/agent_test.py create mode 100644 acme/acme/agents/tf/svg0_prior/learning.py create mode 100644 acme/acme/agents/tf/svg0_prior/networks.py create mode 100644 acme/acme/agents/tf/svg0_prior/utils.py create mode 100644 acme/acme/core.py create mode 100644 acme/acme/core_test.py create mode 100644 acme/acme/datasets/__init__.py create mode 100644 acme/acme/datasets/image_augmentation.py create mode 100644 acme/acme/datasets/numpy_iterator.py create mode 100644 acme/acme/datasets/numpy_iterator_test.py create mode 100644 acme/acme/datasets/reverb.py create mode 100644 acme/acme/datasets/reverb_benchmark.py create mode 100644 acme/acme/datasets/tfds.py create mode 100644 acme/acme/environment_loop.py create mode 100644 acme/acme/environment_loop_test.py create mode 100644 acme/acme/environment_loops/__init__.py create mode 100644 acme/acme/environment_loops/open_spiel_environment_loop.py create mode 100644 acme/acme/environment_loops/open_spiel_environment_loop_test.py create mode 100644 acme/acme/jax/__init__.py create mode 100644 acme/acme/jax/experiments/__init__.py create mode 100644 acme/acme/jax/experiments/config.py create mode 100644 acme/acme/jax/experiments/make_distributed_experiment.py create mode 100644 acme/acme/jax/experiments/make_distributed_offline_experiment.py create mode 100644 acme/acme/jax/experiments/run_experiment.py create mode 100644 acme/acme/jax/experiments/run_offline_experiment.py create mode 100644 acme/acme/jax/imitation_learning_types.py create mode 100644 acme/acme/jax/layouts/__init__.py create mode 100644 acme/acme/jax/layouts/distributed_layout.py create mode 100644 acme/acme/jax/layouts/local_layout.py create mode 100644 acme/acme/jax/layouts/offline_distributed_layout.py create mode 100644 acme/acme/jax/losses/__init__.py create mode 100644 acme/acme/jax/losses/impala.py create mode 100644 acme/acme/jax/losses/impala_test.py create mode 100644 acme/acme/jax/losses/mpo.py create mode 100644 acme/acme/jax/networks/__init__.py create mode 100644 acme/acme/jax/networks/atari.py create mode 100644 acme/acme/jax/networks/base.py create mode 100644 acme/acme/jax/networks/continuous.py create mode 100644 acme/acme/jax/networks/distributional.py create mode 100644 acme/acme/jax/networks/duelling.py create mode 100644 acme/acme/jax/networks/embedding.py create mode 100644 acme/acme/jax/networks/multiplexers.py create mode 100644 acme/acme/jax/networks/policy_value.py create mode 100644 acme/acme/jax/networks/rescaling.py create mode 100644 acme/acme/jax/networks/resnet.py create mode 100644 acme/acme/jax/running_statistics.py create mode 100644 acme/acme/jax/running_statistics_test.py create mode 100644 acme/acme/jax/savers.py create mode 100644 acme/acme/jax/savers_test.py create mode 100644 acme/acme/jax/snapshotter.py create mode 100644 acme/acme/jax/snapshotter_test.py create mode 100644 acme/acme/jax/types.py create mode 100644 acme/acme/jax/utils.py create mode 100644 acme/acme/jax/utils_test.py create mode 100644 acme/acme/jax/variable_utils.py create mode 100644 acme/acme/jax/variable_utils_test.py create mode 100644 acme/acme/multiagent/__init__.py create mode 100644 acme/acme/multiagent/types.py create mode 100644 acme/acme/multiagent/utils.py create mode 100644 acme/acme/multiagent/utils_test.py create mode 100644 acme/acme/specs.py create mode 100644 acme/acme/testing/__init__.py create mode 100644 acme/acme/testing/fakes.py create mode 100644 acme/acme/testing/multiagent_fakes.py create mode 100644 acme/acme/testing/test_utils.py create mode 100644 acme/acme/tf/__init__.py create mode 100644 acme/acme/tf/losses/__init__.py create mode 100644 acme/acme/tf/losses/distributional.py create mode 100644 acme/acme/tf/losses/distributional_test.py create mode 100644 acme/acme/tf/losses/dpg.py create mode 100644 acme/acme/tf/losses/huber.py create mode 100644 acme/acme/tf/losses/mompo.py create mode 100644 acme/acme/tf/losses/mpo.py create mode 100644 acme/acme/tf/losses/quantile.py create mode 100644 acme/acme/tf/losses/r2d2.py create mode 100644 acme/acme/tf/networks/__init__.py create mode 100644 acme/acme/tf/networks/atari.py create mode 100644 acme/acme/tf/networks/base.py create mode 100644 acme/acme/tf/networks/continuous.py create mode 100644 acme/acme/tf/networks/discrete.py create mode 100644 acme/acme/tf/networks/distributional.py create mode 100644 acme/acme/tf/networks/distributional_test.py create mode 100644 acme/acme/tf/networks/distributions.py create mode 100644 acme/acme/tf/networks/distributions_test.py create mode 100644 acme/acme/tf/networks/duelling.py create mode 100644 acme/acme/tf/networks/embedding.py create mode 100644 acme/acme/tf/networks/legal_actions.py create mode 100644 acme/acme/tf/networks/masked_epsilon_greedy.py create mode 100644 acme/acme/tf/networks/multihead.py create mode 100644 acme/acme/tf/networks/multiplexers.py create mode 100644 acme/acme/tf/networks/noise.py create mode 100644 acme/acme/tf/networks/policy_value.py create mode 100644 acme/acme/tf/networks/quantile.py create mode 100644 acme/acme/tf/networks/recurrence.py create mode 100644 acme/acme/tf/networks/recurrence_test.py create mode 100644 acme/acme/tf/networks/rescaling.py create mode 100644 acme/acme/tf/networks/stochastic.py create mode 100644 acme/acme/tf/networks/vision.py create mode 100644 acme/acme/tf/savers.py create mode 100644 acme/acme/tf/savers_test.py create mode 100644 acme/acme/tf/utils.py create mode 100644 acme/acme/tf/utils_test.py create mode 100644 acme/acme/tf/variable_utils.py create mode 100644 acme/acme/tf/variable_utils_test.py create mode 100644 acme/acme/types.py create mode 100644 acme/acme/utils/__init__.py create mode 100644 acme/acme/utils/async_utils.py create mode 100644 acme/acme/utils/counting.py create mode 100644 acme/acme/utils/counting_test.py create mode 100644 acme/acme/utils/experiment_utils.py create mode 100644 acme/acme/utils/frozen_learner.py create mode 100644 acme/acme/utils/frozen_learner_test.py create mode 100644 acme/acme/utils/iterator_utils.py create mode 100644 acme/acme/utils/iterator_utils_test.py create mode 100644 acme/acme/utils/loggers/__init__.py create mode 100644 acme/acme/utils/loggers/aggregators.py create mode 100644 acme/acme/utils/loggers/asynchronous.py create mode 100644 acme/acme/utils/loggers/auto_close.py create mode 100644 acme/acme/utils/loggers/base.py create mode 100644 acme/acme/utils/loggers/base_test.py create mode 100644 acme/acme/utils/loggers/constant.py create mode 100644 acme/acme/utils/loggers/csv.py create mode 100644 acme/acme/utils/loggers/csv_test.py create mode 100644 acme/acme/utils/loggers/dataframe.py create mode 100644 acme/acme/utils/loggers/default.py create mode 100644 acme/acme/utils/loggers/filters.py create mode 100644 acme/acme/utils/loggers/filters_test.py create mode 100644 acme/acme/utils/loggers/image.py create mode 100644 acme/acme/utils/loggers/image_test.py create mode 100644 acme/acme/utils/loggers/terminal.py create mode 100644 acme/acme/utils/loggers/terminal_test.py create mode 100644 acme/acme/utils/loggers/tf_summary.py create mode 100644 acme/acme/utils/lp_utils.py create mode 100644 acme/acme/utils/lp_utils_test.py create mode 100644 acme/acme/utils/metrics.py create mode 100644 acme/acme/utils/observers/__init__.py create mode 100644 acme/acme/utils/observers/action_metrics.py create mode 100644 acme/acme/utils/observers/action_metrics_test.py create mode 100644 acme/acme/utils/observers/action_norm.py create mode 100644 acme/acme/utils/observers/action_norm_test.py create mode 100644 acme/acme/utils/observers/base.py create mode 100644 acme/acme/utils/observers/env_info.py create mode 100644 acme/acme/utils/observers/env_info_test.py create mode 100644 acme/acme/utils/observers/measurement_metrics.py create mode 100644 acme/acme/utils/observers/measurement_metrics_test.py create mode 100644 acme/acme/utils/paths.py create mode 100644 acme/acme/utils/paths_test.py create mode 100644 acme/acme/utils/reverb_utils.py create mode 100644 acme/acme/utils/reverb_utils_test.py create mode 100644 acme/acme/utils/signals.py create mode 100644 acme/acme/utils/tree_utils.py create mode 100644 acme/acme/utils/tree_utils_test.py create mode 100644 acme/acme/wrappers/__init__.py create mode 100644 acme/acme/wrappers/action_repeat.py create mode 100644 acme/acme/wrappers/atari_wrapper.py create mode 100644 acme/acme/wrappers/atari_wrapper_dopamine.py create mode 100644 acme/acme/wrappers/atari_wrapper_test.py create mode 100644 acme/acme/wrappers/base.py create mode 100644 acme/acme/wrappers/base_test.py create mode 100644 acme/acme/wrappers/canonical_spec.py create mode 100644 acme/acme/wrappers/concatenate_observations.py create mode 100644 acme/acme/wrappers/delayed_reward.py create mode 100644 acme/acme/wrappers/delayed_reward_test.py create mode 100644 acme/acme/wrappers/expand_scalar_observation_shapes.py create mode 100644 acme/acme/wrappers/frame_stacking.py create mode 100644 acme/acme/wrappers/frame_stacking_test.py create mode 100644 acme/acme/wrappers/gym_wrapper.py create mode 100644 acme/acme/wrappers/gym_wrapper_test.py create mode 100644 acme/acme/wrappers/mujoco.py create mode 100644 acme/acme/wrappers/multiagent_dict_key_wrapper.py create mode 100644 acme/acme/wrappers/multigrid_wrapper.py create mode 100644 acme/acme/wrappers/noop_starts.py create mode 100644 acme/acme/wrappers/noop_starts_test.py create mode 100644 acme/acme/wrappers/observation_action_reward.py create mode 100644 acme/acme/wrappers/open_spiel_wrapper.py create mode 100644 acme/acme/wrappers/open_spiel_wrapper_test.py create mode 100644 acme/acme/wrappers/single_precision.py create mode 100644 acme/acme/wrappers/single_precision_test.py create mode 100644 acme/acme/wrappers/step_limit.py create mode 100644 acme/acme/wrappers/video.py create mode 100644 acme/docs/_static/custom.css create mode 100644 acme/docs/conf.py create mode 100644 acme/docs/faq.md create mode 100644 acme/docs/imgs/acme-notext.png create mode 100644 acme/docs/imgs/acme.png create mode 100644 acme/docs/index.rst create mode 100644 acme/docs/requirements.txt create mode 100644 acme/docs/user/agents.md create mode 100644 acme/docs/user/components.md create mode 100644 acme/docs/user/diagrams/actor_loop.png create mode 100644 acme/docs/user/diagrams/agent_loop.png create mode 100644 acme/docs/user/diagrams/batch_loop.png create mode 100644 acme/docs/user/diagrams/distributed_loop.png create mode 100644 acme/docs/user/diagrams/environment_loop.png create mode 100644 acme/docs/user/logos/jax-small.png create mode 100644 acme/docs/user/logos/tf-small.png create mode 100644 acme/docs/user/overview.md create mode 100644 acme/examples/README.md create mode 100644 acme/examples/baselines/rl_continuous/helpers.py create mode 100644 acme/examples/baselines/rl_continuous/run_d4pg.py create mode 100644 acme/examples/baselines/rl_continuous/run_ppo.py create mode 100644 acme/examples/baselines/rl_continuous/run_sac.py create mode 100644 acme/examples/baselines/rl_continuous/run_td3.py create mode 100644 acme/examples/baselines/rl_discrete/helpers.py create mode 100644 acme/examples/baselines/rl_discrete/run_dqn.py create mode 100644 acme/examples/baselines/rl_discrete/run_mdqn.py create mode 100644 acme/examples/bsuite/run_dqn.py create mode 100644 acme/examples/bsuite/run_impala.py create mode 100644 acme/examples/bsuite/run_mcts.py create mode 100644 acme/examples/multiagent/multigrid/helpers.py create mode 100644 acme/examples/multiagent/multigrid/run_multigrid.py create mode 100644 acme/examples/offline/bc_utils.py create mode 100644 acme/examples/offline/run_bc.py create mode 100644 acme/examples/offline/run_bc_jax.py create mode 100644 acme/examples/offline/run_bcq.py create mode 100644 acme/examples/offline/run_cql_jax.py create mode 100644 acme/examples/offline/run_crr_jax.py create mode 100644 acme/examples/offline/run_dqfd.py create mode 100644 acme/examples/offline/run_mbop_jax.py create mode 100644 acme/examples/offline/run_offline_td3_jax.py create mode 100644 acme/examples/open_spiel/run_dqn.py create mode 100644 acme/examples/quickstart.ipynb create mode 100644 acme/examples/tf/control_suite/helpers.py create mode 100644 acme/examples/tf/control_suite/lp_d4pg.py create mode 100644 acme/examples/tf/control_suite/lp_ddpg.py create mode 100644 acme/examples/tf/control_suite/lp_dmpo.py create mode 100644 acme/examples/tf/control_suite/lp_dmpo_pixels.py create mode 100644 acme/examples/tf/control_suite/lp_dmpo_pixels_drqv2.py create mode 100644 acme/examples/tf/control_suite/lp_mpo.py create mode 100644 acme/examples/tutorial.ipynb create mode 100755 acme/setup.py create mode 100755 acme/test.sh create mode 100644 aco/DeepSwarm/.gitignore create mode 100644 aco/DeepSwarm/LICENSE create mode 100644 aco/DeepSwarm/Makefile create mode 100644 aco/DeepSwarm/README.md create mode 100644 aco/DeepSwarm/deepswarm/__init__.py create mode 100644 aco/DeepSwarm/deepswarm/aco.py create mode 100644 aco/DeepSwarm/deepswarm/backends.py create mode 100644 aco/DeepSwarm/deepswarm/deepswarm.py create mode 100644 aco/DeepSwarm/deepswarm/log.py create mode 100644 aco/DeepSwarm/deepswarm/nodes.py create mode 100644 aco/DeepSwarm/deepswarm/storage.py create mode 100644 aco/DeepSwarm/examples/cifar10.py create mode 100644 aco/DeepSwarm/examples/context.py create mode 100644 aco/DeepSwarm/examples/fashion-mnist.py create mode 100644 aco/DeepSwarm/examples/mnist.py create mode 100644 aco/DeepSwarm/requirements.txt create mode 100644 aco/DeepSwarm/setup.py create mode 100644 aco/DeepSwarm/tests/test_aco.py create mode 100644 aco/DeepSwarm/tests/test_graph.py create mode 100644 aco/DeepSwarm/tests/test_nodes.py create mode 100644 aco/DeepSwarm/train.py create mode 100644 activate_arch_gym.sh create mode 100644 bo/CustomBayesOpt.py create mode 100644 bo/CustomEstimator.py create mode 100644 bo/CustomGridSearch.py create mode 100644 bo/DRAMSysEstimator.py create mode 100644 bo/FARSIEnvEstimator.py create mode 100644 bo/MaestroEstimator.py create mode 100644 bo/SniperEstimator.py create mode 100644 bo/TimeloopEstimator.py create mode 100644 configs/configs_random_env.py create mode 100644 data/proxy_model_converter.py create mode 100644 docs/ArchGym_Framework_Overview.png create mode 100644 docs/ArchGym_Overview.pdf create mode 100644 environment.yml create mode 100644 settings/default_dramsys.yaml create mode 100644 settings/default_farsi_limited.yaml create mode 100644 settings/default_maestro.yaml create mode 100644 settings/default_sniper.yaml create mode 100644 settings/default_timeloop.yaml create mode 100644 sko/ACA.py create mode 100644 sko/AFSA.py create mode 100644 sko/DE.py create mode 100644 sko/GA.py create mode 100644 sko/IA.py create mode 100644 sko/PSO.py create mode 100644 sko/SA.py create mode 100644 sko/__init__.py create mode 100644 sko/base.py create mode 100644 sko/demo_func.py create mode 100644 sko/operators/__init__.py create mode 100644 sko/operators/crossover.py create mode 100644 sko/operators/mutation.py create mode 100644 sko/operators/ranking.py create mode 100644 sko/operators/selection.py create mode 100644 sko/operators_gpu/__init__.py create mode 100644 sko/operators_gpu/crossover_gpu.py create mode 100644 sko/operators_gpu/mutation_gpu.py create mode 100644 sko/operators_gpu/ranking_gpu.py create mode 100644 sko/operators_gpu/selection_gpu.py create mode 100644 sko/requirements.txt create mode 100644 sko/tool_kit.py create mode 100644 sko/tools.py create mode 100644 tests/custom-env/random_simpleEnv.py create mode 100644 tests/custom-env/train_simpleEnv.py create mode 100644 tests/rllib/ppo_test.py create mode 100644 tests/rllib/ppo_train.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..09c9a597 --- /dev/null +++ b/.gitignore @@ -0,0 +1,191 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +*.swp +*.pyc +*.py~ +*.bak +.pytest_cache +.DS_Store +.idea +.vscode +.coverage +.coverage.* +__pycache__/ +_build/ +*.npz +*.pth +.pytype/ +git_rewrite_commit_history.sh + +# Setuptools distribution and build folders. +/dist/ +/build +keys/ + +# Virtualenv +/env +/venv + + +*.sublime-project +*.sublime-workspace + +.idea + +logs/ + +.ipynb_checkpoints +ghostdriver.log + +htmlcov + +junk +src + +*.egg-info +.cache +*.lprof +*.prof + +configs/arch_gym_configs.py + +sims/Sniper/docker/SniperLink +sims/Sniper/CPU2017/intspeed +sims/Sniper/CPU2017/fpspeed +sims/Sniper/config + +*.code-workspace + +saves/ + +# Astra-sim and results +sims/AstraSim/astra-sim +sims/AstraSim/results diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9d7547a5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ + +[submodule "sims/AstraSim/astra-sim"] + path = sims/AstraSim/astra-sim + url = https://github.com/astra-sim/astra-sim.git diff --git a/Project_FARSI/.gitignore b/Project_FARSI/.gitignore new file mode 100644 index 00000000..8f5b1848 --- /dev/null +++ b/Project_FARSI/.gitignore @@ -0,0 +1,6 @@ +.idea/ +data_collection/data +data_collection/.DS_Store +*pyc +__pycache__ +specs/database_data/hardcoded diff --git a/Project_FARSI/BUCKCONFIG_FILE_EXISTS.md b/Project_FARSI/BUCKCONFIG_FILE_EXISTS.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/CODE_OF_CONDUCT.md b/Project_FARSI/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..83f431e8 --- /dev/null +++ b/Project_FARSI/CODE_OF_CONDUCT.md @@ -0,0 +1,80 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when there is a +reasonable belief that an individual's behavior may have a negative impact on +the project or its community. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/Project_FARSI/CONTRIBUTING.md b/Project_FARSI/CONTRIBUTING.md new file mode 100644 index 00000000..4261e030 --- /dev/null +++ b/Project_FARSI/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing to Project_FARSI +We want to make contributing to this project as easy and transparent as +possible. + +## Pull Requests +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `main`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + + +## Issues +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style +* 2 spaces for indentation rather than tabs +* 80 character line length +* ... + +## License +By contributing to Project_FARSI , you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. diff --git a/Project_FARSI/DOCUSAURUS_ENABLED.md b/Project_FARSI/DOCUSAURUS_ENABLED.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/DSE_utils/__init__.py b/Project_FARSI/DSE_utils/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Project_FARSI/DSE_utils/__init__.py @@ -0,0 +1 @@ + diff --git a/Project_FARSI/DSE_utils/design_space_exploration_handler.py b/Project_FARSI/DSE_utils/design_space_exploration_handler.py new file mode 100644 index 00000000..0b94b6bb --- /dev/null +++ b/Project_FARSI/DSE_utils/design_space_exploration_handler.py @@ -0,0 +1,630 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. +from zipfile import ZipFile +from os.path import basename +from design_utils.design import * +from DSE_utils import hill_climbing +from specs.data_base import * +from visualization_utils import vis_hardware, vis_stats,plot +import csv +import dill +import pickle +import matplotlib.pyplot as plt +if config.simulation_method == "power_knobs": + from specs import database_input_powerKnobs as database_input +elif config.simulation_method == "performance": + from specs import database_input +else: + raise NameError("Simulation method unavailable") +import random +import pickle + +# class used for deign handling. +# This class uses an exploration algorithm (such as hill climbing to explore the design space) +# specify the exploration algorithm in the config file. +class DSEHandler: + def __init__(self, result_dir=os.getcwd()): + self.check_pointed_best_sim_dps = [] # list of check pointed simulated designs + self.check_pointed_best_ex_dps = [] # list of check pointed example designs + self.dse = None # design space exploration algorithm + self.database = None # data base (contains hw and sw database for mapping/allocation of hw/sw) + self.IP_library = [] + self.result_dir = result_dir + self.check_point_folder_name = "check_points" + self.check_point_ctr = 0 + return None + + # --------------- + # Functionality: + # set up an exploration dse. + # specify the explorer type in the config file + # --------------- + def explore_exhaustively(self, db_input, hw_sampling, system_workers): + mapping_process_id = system_workers[1] + FARSI_gen_process_id = system_workers[3] + + if config.dse_type == "exhaustive": + self.database = DataBase(db_input, hw_sampling) + self.dse = hill_climbing.HillClimbing(self.database, self.result_dir) + + # generate light systems + start = time.time() + all_light_systems = self.dse.dh.light_system_gen_exhaustively(system_workers, self.database) + print("light system generation time: " + str(time.time() - start)) + print("----- all light system generated for process: " + str(mapping_process_id) + "_" + str(FARSI_gen_process_id)) + + # generate FARSI systems + start = time.time() + all_exs = self.dse.dh.FARSI_system_gen_exhaustively(all_light_systems, system_workers) + print("FARSI system generation time: " + str(time.time() - start)) + print("----- all FARSI system generated for process: " + str(mapping_process_id) + "_" + str(FARSI_gen_process_id)) + + # simulate them + start = time.time() + all_sims = [] + for ex_dp in all_exs: + sim_dp = self.dse.eval_design(ex_dp, self.database) + if config.RUN_VERIFICATION_PER_GEN or config.RUN_VERIFICATION_PER_NEW_CONFIG or config.RUN_VERIFICATION_PER_IMPROVMENT: + self.dse.gen_verification_data(sim_dp, ex_dp) + all_sims.append(sim_dp) + + print("simulation time: " + str(time.time() - start)) + print("----- all FARSI system simulated process: " + str(mapping_process_id) + "_" + str(FARSI_gen_process_id)) + + # collect data + latency = [sim.get_dp_stats().get_system_complex_metric("latency") for sim in all_sims] + power = [sim.get_dp_stats().get_system_complex_metric("power") for sim in all_sims] + area = [sim.get_dp_stats().get_system_complex_metric("area") for sim in all_sims] + energy = [sim.get_dp_stats().get_system_complex_metric("energy") for sim in all_sims] + x = range(0, len(latency)) + + # write into a file + base_dir = os.getcwd() + result_dir = os.path.join(base_dir, config.exhaustive_result_dir ) + if not os.path.exists(result_dir): + os.makedirs(result_dir) + + result_in_list_file_addr = os.path.join(result_dir, + config.exhaustive_output_file_prefix + + str(mapping_process_id) +"_" + str(FARSI_gen_process_id) + '.txt') + with open(result_in_list_file_addr, 'w') as f: + f.write('itr_count: ') + for listitem in x: + f.write('%s,' % str(listitem)) + + result_in_pdf_file_addr = os.path.join(result_dir, + 'exhaustive_for_pid' + str(mapping_process_id) + "_" + str( + FARSI_gen_process_id) + '.txt') + with open(result_in_pdf_file_addr, + 'a') as f: + f.write('\n') + f.write('latency: ') + for listitem in latency: + f.write('%s,' % str(listitem)) + + f.write('\n') + f.write('power: ') + for listitem in power: + f.write('%s,' % str(listitem)) + + f.write('\n') + f.write('energy: ') + for listitem in energy: + f.write('%s,' % str(listitem)) + + f.write('\n') + f.write('area: ') + for listitem in area: + f.write('%s,' % str(listitem)) + + # plot + for metric in ["latency", "area", "power", "energy"]: + fig, ax = plt.subplots() + if metric == "latency": + y = [max(list(el.values())) for el in vars()[metric]] + else: + y = vars()[metric] + ax.scatter(x, y, marker="x") + ax.set_xlabel("iteration count") + ax.set_ylabel(metric) + fig.savefig("exhaustive_"+metric+"_for_pid_"+ str(mapping_process_id) +"_" + str(FARSI_gen_process_id) +".pdf") + + print("done") + else: + print("can not explore exhaustively with dse_type:" + config.dse_type) + exit(0) + + # --------------- + # Functionality: + # set up an exploration dse. + # specify the explorer type in the config file + # --------------- + def setup_an_explorer(self, db_input, hw_sampling): + # body + if config.dse_type == "hill_climbing" or config.dse_type == "moos" or config.dse_type == "simple_greedy_one_sample": + exploration_start_time = time.time() # time hooks (for data collection) + self.database = DataBase(db_input, hw_sampling) # initialize the database + # initializes the design space exploration of certain type + self.dse = hill_climbing.HillClimbing(self.database, self.result_dir) + elif config.dse_type == "exhaustive": + print("this main is not suitable for exhaustive search") + exit(0) + #TODO: fix the following. The following commented code is there + # to guide writing the code + """ + self.database = DataBase(database_input.tasksL, database_input.blocksL, + database_input.pe_mapsL, database_input.pe_schedulesL, database_input.SOCsL) + + # change this later + self.dse = hill_climbing.HillClimbing(self.database) + all_exs = self.dse.dh.gen_des_exhaustively() + all_sims = [] + for ex_dp in all_exs: + all_sims.append(self.dse.eval_design(ex_dp, self.database)) + + latency = [sim.get_dp_stats().get_system_complex_metric("latency") for sim in all_sims] + plot.scatter_plot(range(0, len(latency)), latency, latency, self.database) + self.dse = Exhaustive(self.database) + """ + + # populate the IP library from an external source + # mode: {"python", "csv"} + def populate_IP_library(self, mode="python"): + if (mode == "python"): + for task_name,blocksL in self.database.get_mappable_blocksL_to_tasks().items(): + for task in self.database.get_tasks(): + if task.name == task_name: + for blockL_ in blocksL: + IP_library_element = IPLibraryElement() + IP_library_element.set_task(task) + IP_library_element.set_blockL(blockL_) + IP_library_element.generate() + self.IP_library.append(IP_library_element) + + # latency + for metric in ["latency", "energy", "area", "power"]: + IP_library_dict = defaultdict(dict) + for IP_library_element in self.IP_library: + IP_library_dict[IP_library_element.blockL.block_instance_name][IP_library_element.get_task().name] = \ + IP_library_element.get_PPAC()[metric] + + all_task_names = [task.name for task in self.database.get_tasks()] + IP_library_dict_ordered = defaultdict(dict) + for IP_library_key in IP_library_dict.keys(): + for task_name in all_task_names: + if task_name in IP_library_dict[IP_library_key].keys(): # check if exists + IP_library_dict_ordered[IP_library_key][task_name] = IP_library_dict[IP_library_key][task_name] + else: + IP_library_dict_ordered[IP_library_key][task_name] = "NA" + + # writing into a file + fields = ["tasks"] + all_task_names + with open("IP_library_"+metric+".csv", "w") as f: + w = csv.DictWriter(f, fields) + w.writeheader() + for k in IP_library_dict_ordered: + w.writerow({field: IP_library_dict_ordered[k].get(field) or k for field in fields}) + + # --------------- + # Functionality: + # prepare the exploration by either generating an initial design (mode ="from scratch") + # or using a check-pointed design + # Variables: + # init_des_point: design point to boost trap the exploration with + # boost_SOC: choose a better SOC (This is for multiple SOC design. Not activated yet) + # mode: whether to bootstrap exploration from scratch or from an already existing design. + # --------------- + def prepare_for_exploration(self, boost_SOC, starting_exploration_mode="from_scratch", init_des = ""): + # either generate an initial design point(dh.gen_init_des()) or use a check_pointed one + self.dse.gen_init_ex_dp(starting_exploration_mode, init_des) + self.dse.dh.boos_SOC = boost_SOC + + # --------------- + # Functionality: + # explore the design space + # --------------- + def explore(self): + exploration_start_time = time.time() # time hook (data collection) + if config.heuristic_type in ["FARSI", "SA"]: + self.dse.explore_ds() + if config.heuristic_type == "moos": + self.dse.explore_ds_with_moos() + if config.heuristic_type == "simple_greedy_one_sample": + self.dse.explore_simple_greedy_one_sample(self.dse.init_ex_dp) + + # --------------- + # Functionality: + # explore the one design. Basically simulate the design and profile + # --------------- + def explore_one_design(self): + exploration_start_time = time.time() # time hook (data collection) + self.dse.explore_one_design() + + # copy the DSE results to the result dir + def copy_DSE_data(self, result_dir): + # result_dir_specific = os.path.join(result_dirresult_summary") + os.system("cp " + config.latest_visualization + "/*" + " " + result_dir) + + # ------------------------------ + # Functionality: + # write the results into a file + # Variables: + # sim_dp: design point simulation + # result_dir: result directory + # unique_number: a number to differentiate between designs + # file_name: output file name + # ------------------------------ + def write_one_results(self, sim_dp, dse, reason_to_terminate, case_study, result_dir_specific, unique_number, file_name): + """ + def convert_dict_to_parsable_csv(dict_): + list = [] + for k,v in dict_.items(): + list.append(str(k)+"="+str(v)) + return list + """ + + def convert_tuple_list_to_parsable_csv(list_): + result = "" + for k, v in list_: + result += str(k) + "=" + str(v) + "___" + return result + + def convert_dictionary_to_parsable_csv_with_semi_column(dict_): + result = "" + for k, v in dict_.items(): + result += str(k) + "=" + str(v) + ";" + return result + + if not os.path.isdir(result_dir_specific): + os.makedirs(result_dir_specific) + + compute_system_attrs = sim_dp.dp_stats.get_compute_system_attr() + bus_system_attrs = sim_dp.dp_stats.get_bus_system_attr() + memory_system_attrs = sim_dp.dp_stats.get_memory_system_attr() + speedup_dict, speedup_attrs = sim_dp.dp_stats.get_speedup_analysis(dse) + + output_file_minimal = os.path.join(result_dir_specific, file_name + ".csv") + + base_budget_scaling = sim_dp.database.db_input.sw_hw_database_population["misc_knobs"]["base_budget_scaling"] + + # minimal output + if os.path.exists(output_file_minimal): + output_fh_minimal = open(output_file_minimal, "a") + else: + output_fh_minimal = open(output_file_minimal, "w") + for metric in config.all_metrics: + output_fh_minimal.write(metric + ",") + if metric in sim_dp.database.db_input.get_budget_dict("glass").keys(): + output_fh_minimal.write(metric + "_budget" + ",") + output_fh_minimal.write("sampling_mode,") + output_fh_minimal.write("sampling_reduction" + ",") + for metric, accuracy_percentage in sim_dp.database.hw_sampling["accuracy_percentage"]["ip"].items(): + output_fh_minimal.write( + metric + "_accuracy" + ",") # for now only write the latency accuracy as the other + for block_type, porting_effort in sim_dp.database.db_input.porting_effort.items(): + output_fh_minimal.write( + block_type + "_effort" + ",") # for now only write the latency accuracy as the other + + output_fh_minimal.write( + "output_design_status" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("case_study" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("heuristic_type" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("unique_number" + ",") # for now only write the latency accuracy as the other + + output_fh_minimal.write("SA_total_depth,") + output_fh_minimal.write("reason_to_terminate" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "population generation cnt" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("iteration cnt" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("workload_set" + ",") # for now only write the latency accuracy as the other + # output_fh_minimal.write("iterationxdepth number" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("simulation time" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "move generation time" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "kernel selection time" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "block selection time" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "transformation selection time" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "transformation_selection_mode" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("dist_to_goal_all" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "dist_to_goal_non_cost" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("system block count" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("system PE count" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("system bus count" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("system memory count" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("routing complexity" + ",") # for now only write the latency accuracy as the other + # output_fh_minimal.write("area_breakdown_subtype" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("block_impact_sorted" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "kernel_impact_sorted" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "metric_impact_sorted" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("move_metric" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "move_transformation_name" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("move_kernel" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("move_block_name" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("move_block_type" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("move_dir" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("comm_comp" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "high_level_optimization" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + "architectural_principle" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("area_dram" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("area_non_dram" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write("channel_cnt" + ",") # for now only write the latency accuracy as the other + for key, val in compute_system_attrs.items(): + output_fh_minimal.write(str(key) + ",") + for key, val in bus_system_attrs.items(): + output_fh_minimal.write(str(key) + ",") + for key, val in memory_system_attrs.items(): + output_fh_minimal.write(str(key) + ",") + + for key, val in speedup_attrs.items(): + output_fh_minimal.write(str(key) + ",") + + for key, val in speedup_dict.items(): + output_fh_minimal.write(str(key) + "_speedup_analysis" + ",") + + for key, val in base_budget_scaling.items(): + output_fh_minimal.write("budget_scaling_" + str(key) + ",") + + output_fh_minimal.write("\n") + for metric in config.all_metrics: + data_ = sim_dp.dp_stats.get_system_complex_metric(metric) + if isinstance(data_, dict): + data__ = convert_dictionary_to_parsable_csv_with_semi_column(data_) + else: + data__ = data_ + + output_fh_minimal.write(str(data__) + ",") + + if metric in sim_dp.database.db_input.get_budget_dict("glass").keys(): + data_ = sim_dp.database.db_input.get_budget_dict("glass")[metric] + if isinstance(data_, dict): + data__ = convert_dictionary_to_parsable_csv_with_semi_column(data_) + else: + data__ = data_ + output_fh_minimal.write(str(data__) + ",") + + output_fh_minimal.write(sim_dp.database.hw_sampling["mode"] + ",") + output_fh_minimal.write(sim_dp.database.hw_sampling["reduction"] + ",") + for metric, accuracy_percentage in sim_dp.database.hw_sampling["accuracy_percentage"]["ip"].items(): + output_fh_minimal.write( + str(accuracy_percentage) + ",") # for now only write the latency accuracy as the other + for block_type, porting_effort in sim_dp.database.db_input.porting_effort.items(): + output_fh_minimal.write(str(porting_effort) + ",") # for now only write the latency accuracy as the other + + if sim_dp.dp_stats.fits_budget(1): + output_fh_minimal.write("budget_met" + ",") # for now only write the latency accuracy as the other + else: + output_fh_minimal.write("budget_not_met" + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(case_study + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(config.heuristic_type+ ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(unique_number) + ",") # for now only write the latency accuracy as the other + + output_fh_minimal.write(str(config.SA_depth) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(reason_to_terminate) + ",") # for now only write the latency accuracy as the other + + ma = sim_dp.get_move_applied() # move applied + if not ma == None: + sorted_metrics = convert_tuple_list_to_parsable_csv([(el, val) for el, val in ma.sorted_metrics.items()]) + metric = ma.get_metric() + transformation_name = ma.get_transformation_name() + task_name = ma.get_kernel_ref().get_task_name() + block_type = ma.get_block_ref().type + dir = ma.get_dir() + generation_time = ma.get_generation_time() + sorted_blocks = convert_tuple_list_to_parsable_csv( + [(el.get_generic_instance_name(), val) for el, val in ma.sorted_blocks]) + sorted_kernels = convert_tuple_list_to_parsable_csv( + [(el.get_task_name(), val) for el, val in ma.sorted_kernels.items()]) + blk_instance_name = ma.get_block_ref().get_generic_instance_name() + blk_type = ma.get_block_ref().type + + comm_comp = (ma.get_system_improvement_log())["comm_comp"] + high_level_optimization = (ma.get_system_improvement_log())["high_level_optimization"] + exact_optimization = (ma.get_system_improvement_log())["exact_optimization"] + architectural_variable_to_improve = (ma.get_system_improvement_log())["architectural_principle"] + block_selection_time = ma.get_logs("block_selection_time") + kernel_selection_time = ma.get_logs("kernel_selection_time") + transformation_selection_time = ma.get_logs("transformation_selection_time") + else: # happens at the very fist iteration + sorted_metrics = "" + metric = "" + transformation_name = "" + task_name = "" + block_type = "" + dir = "" + generation_time = '' + sorted_blocks = '' + sorted_kernels = {} + blk_instance_name = '' + blk_type = '' + comm_comp = "" + high_level_optimization = "" + architectural_variable_to_improve = "" + block_selection_time = "" + kernel_selection_time = "" + transformation_selection_time = "" + + routing_complexity = sim_dp.dp_rep.get_hardware_graph().get_routing_complexity() + simple_topology = sim_dp.dp_rep.get_hardware_graph().get_simplified_topology_code() + blk_cnt = sum([int(el) for el in simple_topology.split("_")]) + bus_cnt = [int(el) for el in simple_topology.split("_")][0] + mem_cnt = [int(el) for el in simple_topology.split("_")][1] + pe_cnt = [int(el) for el in simple_topology.split("_")][2] + # itr_depth_multiplied = sim_dp.dp_rep.get_iteration_number()*config.SA_depth + sim_dp.dp_rep.get_depth_number() + + output_fh_minimal.write(str( + sim_dp.dp_rep.get_population_generation_cnt()) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + str(dse.get_total_iteration_cnt()) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write('_'.join(sim_dp.database.db_input.workload_tasks.keys()) + ",") + # output_fh_minimal.write(str(itr_depth_multiplied)+ ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + str(sim_dp.dp_rep.get_simulation_time()) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(generation_time) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + str(kernel_selection_time) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(block_selection_time) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + str(transformation_selection_time) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write( + str(config.transformation_selection_mode) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str( + sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power", "cost"], + mode="eliminate")) + ",") + output_fh_minimal.write(str( + sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power"], mode="eliminate")) + ",") + output_fh_minimal.write(str(blk_cnt) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(pe_cnt) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(bus_cnt) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(mem_cnt) + ",") # for now only write the latency accuracy as the other + output_fh_minimal.write(str(routing_complexity) + ",") # for now only write the latency accuracy as the other + # output_fh_minimal.write(convert_dictionary_to_parsable_csv_with_semi_column(sim_dp.dp_stats.SOC_area_subtype_dict.keys()) + ",") + output_fh_minimal.write(str(sorted_blocks) + ",") + output_fh_minimal.write(str(sorted_kernels) + ",") + output_fh_minimal.write(str(sorted_metrics) + ",") + output_fh_minimal.write(str(metric) + ",") + output_fh_minimal.write(transformation_name + ",") + output_fh_minimal.write(task_name + ",") + output_fh_minimal.write(blk_instance_name + ",") + output_fh_minimal.write(blk_type + ",") + output_fh_minimal.write(str(dir) + ",") + output_fh_minimal.write(str(comm_comp) + ",") + output_fh_minimal.write(str(high_level_optimization) + ",") + output_fh_minimal.write(str(architectural_variable_to_improve) + ",") + output_fh_minimal.write(str(sim_dp.dp_stats.get_system_complex_area_stacked_dram()["dram"]) + ",") + output_fh_minimal.write(str(sim_dp.dp_stats.get_system_complex_area_stacked_dram()["non_dram"]) + ",") + output_fh_minimal.write(str(sim_dp.dp_rep.get_hardware_graph().get_number_of_channels()) + ",") + for key, val in compute_system_attrs.items(): + output_fh_minimal.write(str(val) + ",") + for key, val in bus_system_attrs.items(): + output_fh_minimal.write(str(val) + ",") + for key, val in memory_system_attrs.items(): + output_fh_minimal.write(str(val) + ",") + + for key, val in speedup_attrs.items(): + output_fh_minimal.write(str(val) + ",") + + for key, val in speedup_dict.items(): + output_fh_minimal.write(convert_dictionary_to_parsable_csv_with_semi_column(val) + ",") + + for key, val in base_budget_scaling.items(): + output_fh_minimal.write(str(val) + ",") + + output_fh_minimal.close() + + def write_data(self, unique_number, result_folder, case_study, current_process_id, total_process_cnt, ctr): + # write the results in the general folder + result_dir_specific = os.path.join(result_folder, "result_summary") + self.write_one_results(self.dse.so_far_best_sim_dp, self.dse, self.dse.reason_to_terminate, case_study, + result_dir_specific, unique_number, + config.FARSI_simple_run_prefix + "_" + str(current_process_id) + "_" + str(total_process_cnt)) + self.dse.write_data_log(list(self.dse.get_log_data()), self.dse.reason_to_terminate, case_study, result_dir_specific, unique_number, + config.FARSI_simple_run_prefix + "_" + str(current_process_id) + "_" + str(total_process_cnt)) + + # write the results in the specific folder + result_folder_modified = result_folder+ "/runs/" + str(ctr) + "/" + os.system("mkdir -p " + result_folder_modified) + self.copy_DSE_data(result_folder_modified) + self.write_one_results(self.dse.so_far_best_sim_dp, self.dse, self.dse.reason_to_terminate, case_study, result_folder_modified, unique_number, + config.FARSI_simple_run_prefix + "_" + str(current_process_id) + "_" + str(total_process_cnt)) + + os.system("cp " + config.home_dir+"/settings/config.py"+ " "+ result_folder) + + # --------------- + # Functionality: + # check point the best design. Check pointing allows to iteratively improve the design by + # using the best of the last iteration design. + # --------------- + def check_point_best_design(self, unique_number): + # deactivate check point to prevent running out of memory + if not config.check_pointing_allowed: + return + + # pickle the results for (out of run) verifications. + # make a directory according to the data/time + date_time = datetime.now().strftime('%m-%d_%H-%M_%S') + result_folder = os.path.join(self.result_dir, self.check_point_folder_name) + #date_time + "_" + str(unique_number)) + if not os.path.exists(result_folder): + os.makedirs(result_folder) + # pickle the results in it + if "ex" in config.check_point_list: + zip_file_name = 'ex_dp_pickled.zip' + zip_file_addr = os.path.join(result_folder, zip_file_name) + pickle_file_name = "ex_dp_pickled"+".txt" + pickle_file_addr = os.path.join(result_folder,pickle_file_name) + ex_dp_pickled_file = open(pickle_file_addr, "wb") + dill.dump(self.dse.so_far_best_ex_dp, ex_dp_pickled_file) + ex_dp_pickled_file.close() + + # remove the old zip file + if os.path.isfile(zip_file_addr): + os.remove(zip_file_addr) + + zipObj = ZipFile(zip_file_addr, 'w') + # Add multiple files to the zip + zipObj.write(pickle_file_addr, basename(pickle_file_addr)) + # close the Zip File + zipObj.close() + + # remove the pickle file + os.remove(pickle_file_addr) + + if "db" in config.check_point_list: + #database_pickled_file = open(os.path.join(result_folder, "database_pickled"+".txt"), "wb") + #dill.dump(self.database, database_pickled_file) + #database_pickled_file.close() + zip_file_name = 'database_pickled.zip' + zip_file_addr = os.path.join(result_folder, zip_file_name) + pickle_file_name = "database_pickled"+".txt" + pickle_file_addr = os.path.join(result_folder,pickle_file_name) + database_pickled_file = open(pickle_file_addr, "wb") + dill.dump(self.database, database_pickled_file) + #dill.dump(self.dse.so_far_best_ex_dp, ex_dp_pickled_file) + database_pickled_file.close() + + # remove the old zip file + if os.path.isfile(zip_file_addr): + os.remove(zip_file_addr) + + zipObj = ZipFile(zip_file_addr, 'w') + # Add multiple files to the zip + zipObj.write(pickle_file_addr, basename(pickle_file_addr)) + # close the Zip File + zipObj.close() + + # remove the pickle file + os.remove(pickle_file_addr) + + if "sim" in config.check_point_list: + sim_dp_pickled_file = open(os.path.join(result_folder, "sim_dp_pickled"+".txt"), "wb") + dill.dump(self.dse.so_far_best_sim_dp, sim_dp_pickled_file) + sim_dp_pickled_file.close() + vis_hardware.vis_hardware(self.dse.so_far_best_ex_dp, config.hw_graphing_mode, result_folder) + + if "counters" in config.check_point_list: + counters_pickled_file = open(os.path.join(result_folder, "counters_pickled" + ".txt"), "wb") + dill.dump(self.dse.counters, counters_pickled_file) + counters_pickled_file.close() + #vis_hardware.vis_hardware(self.dse.so_far_best_ex_dp, config.hw_graphing_mode, result_folder) + + for key, val in self.dse.so_far_best_sim_dp.dp_stats.SOC_metric_dict["latency"]["glass"][0].items(): + print("lat is {} for {}".format(val, key)) + burst_size = config.default_burst_size + queue_size = config.default_data_queue_size + print("burst size is {}".format(burst_size)) + print("queue size is {}".format(queue_size)) + + #self.dse.write_data_log(list(self.dse.get_log_data()), self.dse.reason_to_terminate, "", result_folder, self.check_point_ctr, + # config.FARSI_simple_run_prefix) + self.check_point_ctr +=1 diff --git a/Project_FARSI/DSE_utils/exhaustive_DSE.py b/Project_FARSI/DSE_utils/exhaustive_DSE.py new file mode 100644 index 00000000..0f9dc4ab --- /dev/null +++ b/Project_FARSI/DSE_utils/exhaustive_DSE.py @@ -0,0 +1,1190 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. + +from sympy.functions.combinatorial import numbers +from sympy import factorial +import math +import itertools +from copy import * +import time +import numpy as np +import operator +import collections + +# ------------------------------ +# Functionality: +# calculate stirling values, ie., the number of ways to partition a set. For mathematical understanding refer to +# https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind +# Variables: +# n, k are both stirling inputs. refer to: +# https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind +# ------------------------------ +# n: balls, k: bins +def calc_stirling(n, k): + # multiply by k! if you want the boxes to at least contain 1 value + return numbers.stirling(n, k, d=None, kind=2, signed=False) + + +# ------------------------------ +# Functionality: +# calculate the migration cardinality +# where we can migrate such that tasks can be distributed accross n+1 blocks when we introduce +# n new blocks (with the restriction that each block needs to have at least one task on it) +# Variables: +# num_tasks: number of tasks within the workload. +# num_blcks_to_split_to: number of blocks to maps the tasks to. +# ------------------------------ +def calc_mig_comb_cnt(num_task, num_blcks_to_split_to): + return factorial(num_blcks_to_split_to) * calc_stirling(num_task, num_blcks_to_split_to) + + +def calc_mig_comb_idenitical_blocks_cnt(num_task, num_blcks_to_split_to): + return calc_stirling(num_task, num_blcks_to_split_to) + +# ------------------------------ +# Functionality: +# calculates the upper bound combination associated with the reduced contention options +# Variables: +# num_tasks: number of tasks within the workload. +# num_blcks_to_split_to: number of blocks to maps the tasks to. +# bocks_to_choose_from: total number of blocks that we can choose our num_blcks_to_split_to set from. +# ------------------------------ +def calc_red_cont_up_comb_cnt(num_tasks, num_blcks_to_split_to, blocks_to_choose_from): + allocation_cardinality = (num_blcks_to_split_to - 1)**blocks_to_choose_from + # the reason that this is upper bound is because if a hardware configuration + # uses two blocks of the same kind, then migration can results in a setup that has been already seen. + return allocation_cardinality * calc_mig_comb_cnt(num_tasks, num_blcks_to_split_to) + +# ------------------------------ +# Functionality: +# give tne number of draws from a population, what is the statistical expected coverage value. +# Variables: +# population_size: statistical population cardinality. +# num_of_draws_threshold: bounding the number of samples drawn from the population. +# ------------------------------ +def calc_coverage_exepctation_value(population_size, num_of_draws): + expected_value_of_coverage = population_size * (1 - ((population_size-1)/population_size)**num_of_draws) + return float(expected_value_of_coverage) + +# ------------------------------ +# Functionality: +# Find how many samples we need to draw from the population to achieve a certain coverage. Note that +# we use the expected value of the coverage, that is, on AVERAGE, what the coverage is if we make certain +# num of draws. +# Variables: +# population_size: statistical population cardinality. +# desired_coverage: which percentage of the population size we'd like to cover. +# num_of_draws_threshold: bounding the number of samples drawn from the population. +# num_of_draw_incr: incrementally increase num_of-draws_threshold to meet the desired coverage. +# ------------------------------ +def find_num_draws_to_satisfy_coverage(population_size, desired_coverage, num_of_draws_threshold, num_of_draw_incr): + coverage_satisfied = False + for num_of_draws in range(0, num_of_draws_threshold, num_of_draw_incr): + coverage_expectation = calc_coverage_exepctation_value(population_size, num_of_draws) + if coverage_expectation >= desired_coverage: + coverage_satisfied = True + break + return coverage_satisfied, coverage_expectation, num_of_draws + +# ------------------------------ +# Functionality: +# simple test for sanity check. +# ------------------------------ +def simple_test(): + pop_size = calc_red_cont_up_comb_cnt(30, 2, 5) + coverage_percentage = 0.50 + x, y, z = find_num_draws_to_satisfy_coverage(pop_size, coverage_percentage*pop_size, 4000, 500) + print(x) + +# we don't consider the infeasibility of allocating +# extra blocks despite of no-parallelism +def system_variation_count_1(gen_config): + MAX_TASK_CNT = gen_config["MAX_TASK_CNT"] + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + + # assuming that we have 5 different tasks and hence (can have up to 5 different blocks). + # we'd like to know how many different migration/allocation combinations are out there. + # Assumptions: + # PE's are identical. + # Buses are identical + # memory is ignored for now + + # topologies + MAX_PE_CNT = MAX_TASK_CNT + MAX_BUS_CNT = MAX_TASK_CNT + task_cnt = MAX_TASK_CNT + system_variations = 0 + for bus_cnt in range(1, MAX_BUS_CNT+1): + for pe_cnt in range(bus_cnt, MAX_PE_CNT+1): + for mem_cnt in range(bus_cnt, pe_cnt+ 1): + # first calculate the topological variations (split) + topo_incr_1 = calc_stirling(pe_cnt, bus_cnt)*factorial(bus_cnt) # at least one pe per bus + # factorial is used because we + # assume distinct buses because the + # the relative positioning of the buses + # impacts the topology + topo_incr_2 = calc_stirling(mem_cnt, bus_cnt)*factorial(bus_cnt) # at least one memory for + # each bus. Note that this estimation + # is a bit conservative as + # scenarios where number of mems + # exceeds the number of pes connected + # to a bus are not really reasonable + # however, they are considered here. + + # then calculate mapping (migrate) + mapping = calc_stirling(task_cnt, pe_cnt)*factorial(pe_cnt) + # then calculate customization (swap) + swap = math.pow(2, (DB_MAX_BUS_CNT + DB_MAX_PE_CNT + DB_MAX_MEM_CNT)) + system_variations += topo_incr_1*topo_incr_2*mapping*swap + + #print("number of system variations: " + str(float(system_variations))) + #print("exhaustive simulation time (hours): " + str(float(system_variations)/(20*3600))) + return system_variations + #print("{:e}".format(system_variations)) + + +class SYSTEM_(): + def __init__(self): + self.bus_cnt = 0 + self.pe_cnt = 0 + self.mem_cnt = 0 + self.similar_system_cnt = 0 + self.task_cnt = 0 + self.par_task_cnt = 0 + self.pe_set = [] # lay buses as the reference and decorate with mem + self.mem_set = [] + self.mapping_variation_cnt = 0 + self.PE_list = [] + self.MEM_list = [] + self.BUS_list = [] + self.BUS_PE_list = {} # dictionary of bus index and the PEs hanging from them. buses are indexed based on BUS_list + self.BUS_MEM_list = {} # dictionary of bus index and the MEMs hanging from them. buses are indexed based on BUS_list + + def set_bus_cnt(self, bus_cnt): + self.bus_cnt = bus_cnt + + def set_pe_cnt(self, pe_cnt): + self.pe_cnt = pe_cnt + + def set_mem_cnt(self, mem_cnt): + self.mem_cnt = mem_cnt + + def get_bus_cnt(self): + return self.bus_cnt + + def get_pe_cnt(self): + return self.pe_cnt + + def get_mem_cnt(self): + return self.mem_cnt + + def append_pe_set(self, pe_cnt): + self.pe_set.append(pe_cnt) + + def set_pe_set(self, pe_set): + self.pe_set = pe_set + self.pe_cnt = sum(self.get_pe_set()) + + def get_mem_set(self): + return self.mem_set + + def set_mem_set(self, mem_set): + self.mem_set = mem_set + self.mem_cnt = sum(self.get_mem_set()) + + def get_pe_set(self): + return self.pe_set + + def set_task_cnt(self, task_cnt): + self.task_cnt = task_cnt + + def set_par_task_cnt(self, par_task_cnt): + self.par_task_cnt = par_task_cnt + + def parallelism_check(self, gen_config): + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + + par_task_cnt = self.par_task_cnt + for pe_cnt in self.get_pe_set(): + if pe_cnt - DB_MAX_PE_CNT > 0: + par_task_cnt -= (pe_cnt-DB_MAX_PE_CNT) + if par_task_cnt < 0: + return False + + return True + + # can't have more memory hanging from bus than it's pe's + def pe_mem_check(self): + for idx in range(0, len(self.get_pe_set())): + if self.get_mem_set()[idx] > self.get_pe_set()[idx]: + return False + + return True + + # can't have more pe's than the number of tasks + def pe_cnt_check(self, gen_config): + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + if self.get_pe_cnt() > MAX_TASK_CNT: + return False + return True + + def set_pe_task_set(self, pe_task_set): + self.pe_task_set = pe_task_set + + def set_mem_task_set(self, mem_task_set): + self.mem_task_set = mem_task_set + + def get_mem_task_set(self): + return self.mem_task_set + + # get tasks of a bus + def get_task_per_bus(self): + bus_tasks = [] + flattened_indecies = self.flatten_indecies(self.pe_set) + for bus_idx in range(0, len(self.get_pe_set())): + list_unflattened = self.get_pe_task_set()[flattened_indecies[bus_idx]:flattened_indecies[bus_idx+1]] + list_flattened = list(itertools.chain(*list_unflattened)) + bus_tasks.append(list_flattened) + + return bus_tasks + + def get_pe_task_set(self): + return self.pe_task_set + + def get_task_s_pe(self, task_name): + for idx, el in enumerate(self.get_pe_task_set()): + if task_name in el: + return idx + + print("task not found") + return -1 + + def get_task_s_mem(self, task_name): + for idx, el in enumerate(self.get_mem_task_set()): + if task_name in el: + return idx + + print("task not found") + return -1 + + def set_PE_list(self, PE_list): + self.PE_list = PE_list + + def flatten_indecies(self, list): + result = [0] + for el in list: + result.append(result[-1]+el) + return result + + def set_BUS_PE_list(self, PE_list): + flattened_indecies = self.flatten_indecies(self.pe_set) + for idx, BUS in enumerate(self.BUS_list): + self.BUS_PE_list[idx] = PE_list[flattened_indecies[idx]: flattened_indecies[idx + 1]] + + def set_BUS_MEM_list(self, MEM_list): + flattened_indecies = self.flatten_indecies(self.mem_set) + for idx, BUS in enumerate(self.BUS_list): + self.BUS_MEM_list[idx] = MEM_list[flattened_indecies[idx]: flattened_indecies[idx + 1]] + + def get_BUS_list(self): + return self.BUS_list + + def get_BUS_PE_list(self): + return self.BUS_PE_list + + def get_BUS_MEM_list(self): + return self.BUS_MEM_list + + # idx index of the bus we need to know the neighbors for + def get_bus_s_pe_neighbours(self, idx): + return self.BUS_PE_list[idx] + + def get_bus_s_mem_neighbours(self, idx): + return self.BUS_MEM_list[idx] + + def set_MEM_list(self, MEM_list): + self.MEM_list = MEM_list + + def set_BUS_list(self, PE_list): + self.BUS_list = PE_list + + # ----------------- + # system counters + # ----------------- + # at the moment only considering bus and PE (assuming that mem would scale with bus. not the the size but bandwidth) + def simple_customization_variation_cnt(self, gen_config): + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + + return pow(DB_MAX_PE_CNT, self.get_pe_cnt())*pow(DB_MAX_BUS_CNT, self.get_bus_cnt())*pow(DB_MAX_MEM_CNT, self.get_mem_cnt()) + + def calc_customization_variation_cnt(self, gen_config): + return self.simple_customization_variation_cnt(gen_config) + + # at the moment, we are considering the upper bounds for the cuts, + # so in reality, there is a gonna be smaller to cuts. + def design_space_reduction(self, gen_config, task_cnt=1): + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + MAX_PAR_TASK_CNT = gen_config["MAX_PAR_TASK_CNT"] + MAX_TASK_CNT = gen_config["MAX_TASK_CNT"] + + if DB_MAX_BUS_CNT - task_cnt <= 0: + return pow(DB_MAX_BUS_CNT - 1, task_cnt)/pow(DB_MAX_BUS_CNT, task_cnt) + else: + return (DB_MAX_BUS_CNT-task_cnt)/DB_MAX_BUS_CNT # the task resides in one of the PEs, + # for that one decrement. You need to look at it + # per mapping scenarios. + # note that if task_cnt > 1, + # calculation is a bit harder as it depends on whether + # tasks are binded to the same PE or not. We + # make this assumption since this will results in more conservative (less) + # DS reduction + + + # map the tasks to the pes + # each pe needs to have at least one task + # we get rid of per bus scheduling combinations since PEs + # are not distinguished yet (note that we can't do this across buses + # as the topology (placement) already makes PE's within each bus + # different relative to others + def simple_mapping_variation_cnt(self, gen_config): + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + + bins = self.get_pe_cnt() + balls = MAX_TASK_CNT + comb_each_pe_at_least_one_task = calc_stirling(balls, bins)*numbers.factorial(bins) + for pe_cnt in self.get_pe_set(): # since PEs are still the same, get rid of the combinations per bus + comb_each_pe_at_least_one_task /= numbers.factorial(pe_cnt) + + return comb_each_pe_at_least_one_task + + def calc_mapping_variation_cnt(self, gen_config): + return self.simple_mapping_variation_cnt(gen_config) + + def system_get_mapping_variation_cnt(self): + return self.mapping_variation_cnt + +#------------------------- +# system counters +#------------------------- +def system_variation_count_2(gen_config): + full_potential_tasks_list = gen_config["full_potential_tasks_list"] + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_PE_list = gen_config["DB_PE_list"] + DB_MEM_list = gen_config["DB_MEM_list"] + DB_BUS_list = gen_config["DB_BUS_list"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + MAX_PAR_TASK_CNT = gen_config["MAX_PAR_TASK_CNT"] + MAX_TASK_CNT = gen_config["MAX_TASK_CNT"] + + # mapping assisted topology + task_cnt = MAX_TASK_CNT + task_list = full_potential_tasks_list[0:MAX_PAR_TASK_CNT-1] + + system_variations = 0 + system_list = [] + + #------------------ + # spawn different system topologies + #------------------ + all_lists = [] + for bus_cnt in range(1, max((DB_MAX_PE_CNT*DB_MAX_BUS_CNT), MAX_PAR_TASK_CNT) + 1): + # generate all the permutations of the pe values + all_lists = [list(range(1, max(DB_MAX_PE_CNT, MAX_PAR_TASK_CNT) + 1))] * bus_cnt + all_lists_permuted = list(itertools.product(*all_lists)) + + # now generated a system with each permutation + for pe_set in all_lists_permuted: + system_ = SYSTEM_() + system_.set_bus_cnt(bus_cnt) + system_.set_pe_set(list(pe_set)) + system_list.append(system_) + + # generate software + for system in system_list: + system.set_task_cnt(MAX_TASK_CNT) + system.set_par_task_cnt(MAX_PAR_TASK_CNT) + + + #------------------ + # filter out infeasible/unreasonable systems + #------------------ + filtered_system_list = [] + for system in system_list: + if not system.pe_cnt_check(): + continue + if not system.parallelism_check(): + continue + + filtered_system_list.append(system) + + + #------------------ + # generate different mapping associated with the system topologies + #------------------ + total_system_variation = 0 + for system in filtered_system_list: + mapping_variations = system.calc_mapping_variation_cnt(gen_config) + + #total_systems = sum([system_.system_get_mapping_variation_cnt() for system_ in system_list]) + #print("number of system variations :" + str(total_system_variation)) + return total_system_variation + #print("exhaustive simulation time (hours):" + str(float(total_system_variation)/(20*3600))) + + +def get_DS_cnt(DS_mode, gen_config, output_mode): + if DS_mode == "exhaustive_naive": + DS_size = system_variation_count_1(gen_config) + elif DS_mode == "exhaustive_reduction_DB_semantics": + DS_size = system_variation_count_2() + + if DS_output_mode == "DS_time": + return DS_size*sim_time_per_design + else: + return DS_size + + +#------------------------- +# system generators +#-------------------------- +# binning balls in to bins where there is +# at least one ball in every bin +def binning(bin_cnt, balls): + # use product + for indices in itertools.product(range(0, bin_cnt), repeat=len(balls)): + result = [[]for _ in range (0, bin_cnt)] + for ball_index, bin_index in enumerate(indices): + result[bin_index].append(balls[ball_index]) + + # discard if any of the bins are empty + res_valid = True + for el in result: + if len(el) == 0: + res_valid = False + if res_valid: + yield result + # yield indices + else: + continue + +# this is for parrallelizaton. it attempts to spreads the workload across the processes equally. +# this can not be perfectly equal as we parallelize based on mapped tasks (and not customized tasks). So, +# there is gonna be imbalances, but this is still better than no parallelization +def shard_work_equally(mapping_customization_variation_cnt_list, process_cnt): + process_id_work_bound_dict = {} + total_mapping = sum([el[0] for el in mapping_customization_variation_cnt_list]) + ideal_work_per_process = sum([el[0]*el[1] for el in mapping_customization_variation_cnt_list])/process_cnt + map_idx_list = [0] + total_system_accumulated = 0 + mapped_system_accumulated = 0 + for map, cust in mapping_customization_variation_cnt_list: + for i in range(1, map+1): + if total_system_accumulated + cust > ideal_work_per_process: + map_idx_list.append(mapped_system_accumulated + max((i-1), 0)) + total_system_accumulated = 0 + else: + total_system_accumulated += cust + + mapped_system_accumulated +=map + + if len(map_idx_list) < process_cnt + 1: + map_idx_list.append(total_mapping) + + for process_id in range(0, len(map_idx_list)-1): + process_id_work_bound_dict[process_id] = (map_idx_list[process_id], map_idx_list[process_id +1]) + + return process_id_work_bound_dict + + +# second map tasks to the mems +def generate_mem_mapping(mapped_systems, process_id_work_dictionary, process_id): + mapped_systems_completed = [] + if not (process_id in process_id_work_dictionary.keys()): # if we couldn't shard properly + exit(0) + system_lower_bound = process_id_work_dictionary[process_id][0] + system_upper_bound = process_id_work_dictionary[process_id][1] + for system in mapped_systems[system_lower_bound: system_upper_bound]: + exessive_memory = False # flaggign scenarios where not enough tasks to mapp to the memoroies + isolated_siink = False + all_task_mappings = [] # all the mappings to the memories + for tasks, mem_cnt in zip(system.get_task_per_bus(), system.get_mem_set()): + + mappings = list(binning(mem_cnt, tasks)) # generating half designs + + # this covers scenarios where there are too many memories, so + # we can distribute tasks to them + if len(mappings) == 0: + exessive_memory = True + break + else: + task_names_ = mappings[0][0] + if any([True for name in task_names_ if "siink" in name]) and len(mappings[0][0]) == 1: # siink can't be occuipying any memory in isolation as it doesn't use any memory + isolated_siink = True + break + else: + all_task_mappings.append(mappings) + + #task_mapping_filtered.append(remove_permutation_per_bus_2(all_task_mappings, system.get_mem_set())) + + if exessive_memory or isolated_siink: # too many memories. One memory would be task less + continue + + all_permutations_tuples = list(itertools.product(*all_task_mappings)) + all_permutations_listified = []#all_permutations_tuples + + + for design in all_permutations_tuples: + new_design = list(design) + #if len(design) == 1: # tuple of size 1: + # new_design.append([[]]) + all_permutations_listified.append(new_design) + + all_permutations_listified_flattened = [] + for design in all_permutations_listified: + #all_tasks = [] # for debugging + design_fixed = [] + for bus_s_mems in design: + for mem in bus_s_mems: + design_fixed.append(mem) + #all_tasks.extend(mem) + all_permutations_listified_flattened.append(design_fixed) + + """ + for task in full_potential_tasks_list: + if not task in all_tasks: + print("what") + """ + + task_mapping_filtered = remove_permutation_per_bus_2(all_permutations_listified_flattened, system.get_mem_set()) + for task_mapping in task_mapping_filtered: + system_ = deepcopy(system) + system_.set_mem_task_set(task_mapping) + mapped_systems_completed.append(system_) + + mapped_systems_completed_filtered = [] # filter scenarios that siink is by itself on memory + for system in mapped_systems_completed: + isolated_siink = False + for tasks_on_mem in system.get_mem_task_set(): + if any([True for task in tasks_on_mem if ("siink" in task and len(tasks_on_mem) == 1)]): + isolated_siink = True + break + if isolated_siink: + continue + mapped_systems_completed_filtered.append(system) + return mapped_systems_completed_filtered + # """ # comment in this if you don't care about task to memory mapping + + +# generate all customization scenarios +def generate_customization(mapped_systems, gen_config): + full_potential_tasks_list = gen_config["full_potential_tasks_list"] + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_PE_list = gen_config["DB_PE_list"] + DB_MEM_list = gen_config["DB_MEM_list"] + DB_BUS_list = gen_config["DB_BUS_list"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + + customized_systems = [] + + for idx, system in enumerate(mapped_systems): + pe_cnt = system.get_pe_cnt() + mem_cnt = system.get_mem_cnt() + bus_cnt = system.get_bus_cnt() + PE_scenarios = list(itertools.product(DB_PE_list, repeat=pe_cnt)) + MEM_scenarios = list(itertools.product(DB_MEM_list, repeat=mem_cnt)) + BUS_scenarios = list(itertools.product(DB_BUS_list, repeat=bus_cnt)) + customization_scenarios = list(itertools.product(PE_scenarios, MEM_scenarios, BUS_scenarios)) + for customized_scn in customization_scenarios: + system_ = deepcopy(system) + + system_.set_BUS_list(list(customized_scn[2])) + system_.set_BUS_PE_list(list(customized_scn[0])) + system_.set_BUS_MEM_list(list(customized_scn[1])) + + #system_.set_PE_list(list(customized_scn[0])) + #system_.set_MEM_list(list(customized_scn[1])) + #system_.set_BUS_list(list(customized_scn[2])) + + # quick sanity check + for task_name in full_potential_tasks_list: + if system.get_task_s_mem(task_name) == -1: + print("something went wrong") + exit(0) + customized_systems.append(system_) + return customized_systems + + +def generate_topologies(gen_config): + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + MAX_PAR_TASK_CNT = gen_config["DB_MAX_PAR_TASK_CNT"] + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + + DB_MIN_PE_CNT = gen_config["DB_MIN_PE_CNT"] + DB_MIN_MEM_CNT = gen_config["DB_MIN_MEM_CNT"] + DB_MIN_BUS_CNT = gen_config["DB_MIN_BUS_CNT"] + #DB_MAX_SYSTEM_to_investigate = gen_config["DB_MAX_SYSTEM_to_investigate"] + + #DB_MIN_PE_CNT = 1 + #DB_MIN_MEM_CNT = 1 + #DB_MIN_BUS_CNT = 1 + + + + system_list = [] + all_lists = [] + for bus_cnt in range(DB_MIN_BUS_CNT, min(DB_MAX_BUS_CNT, MAX_PAR_TASK_CNT) + 1): + # generate all the permutations of the pe values + all_lists = [list(range(1, min(DB_MAX_PE_CNT, MAX_PAR_TASK_CNT) + 1))] * bus_cnt + pe_dist_perm = list(itertools.product(*all_lists)) + + # some filtering, otherwise never finish + pe_dist_perm_filtered = [] + for pe_dist in pe_dist_perm: + if sum(pe_dist) <= MAX_TASK_CNT and sum(pe_dist) >= DB_MIN_PE_CNT and sum(pe_dist)<= DB_MAX_PE_CNT: + pe_dist_perm_filtered.append(pe_dist) + + pe_mem_dist_perm = list(itertools.product(pe_dist_perm_filtered, repeat=2)) + + # now generated a system with each permutation + for pe_set, mem_set in pe_mem_dist_perm: + system_ = SYSTEM_() + system_.set_bus_cnt(bus_cnt) + system_.set_pe_set(list(pe_set)) + system_.set_mem_set(list(mem_set)) + system_list.append(system_) + + # add some sw information + for system in system_list[:len(system_list)]: + system.set_task_cnt(MAX_TASK_CNT) + system.set_par_task_cnt(MAX_PAR_TASK_CNT) + + #------------------ + # filter out infeasible/unreasonable topologies: using parallelism/customization at the moment + #------------------ + filtered_system_list = [] + for system in system_list: + if not system.pe_cnt_check(gen_config): + continue + if not system.parallelism_check(gen_config): + continue + #if not system.pe_mem_check(): + # continue + filtered_system_list.append(system) + + return filtered_system_list[:min(len(filtered_system_list), gen_config["DB_MAX_SYSTEM_to_investigate"])] + +# count all the mapping and customizations +def count_mapping_customization(system_topologies, gen_config): + mapping_customization_variation_cnt_list = [] # keep track of how many mapping variations per topology + total_system_variation_cnt = 0 + for system in system_topologies: + # get system counts per topology (for sanity checks) + mapping_variation_cnt = system.calc_mapping_variation_cnt(gen_config) + customization_variation_cnt = system.calc_customization_variation_cnt(gen_config) + # customization_variation_cnt = 1 + total_system_variation_cnt += mapping_variation_cnt*customization_variation_cnt + mapping_customization_variation_cnt_list.append((mapping_variation_cnt, customization_variation_cnt)) + #print(sum([el[0] for el in mapping_variation_cnt_list])) + return mapping_customization_variation_cnt_list, total_system_variation_cnt + + +# generate all the mappings for the topologies specified in filtered_system_list +def generate_pe_mapping(filtered_system_list, gen_config): + full_potential_tasks_list = gen_config["full_potential_tasks_list"] + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + MAX_PAR_TASK_CNT = gen_config["DB_MAX_PAR_TASK_CNT"] + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + DB_MAX_PE_CNT_range = gen_config["DB_MAX_PE_CNT_range"] + MAX_TASK_range = gen_config["DB_MAX_TASK_CNT_range"] + MAX_TASK_CNT_range = gen_config["DB_MAX_TASK_CNT_range"] + MAX_PAR_TASK_CNT_range = gen_config["DB_MAX_PAR_TASK_CNT_range"] + DS_output_mode = gen_config["DS_output_mode"] + DB_MAX_SYSTEM_to_investigate = gen_config["DB_MAX_SYSTEM_to_investigate"] + + + mapping_customization_variation_cnt_list = [] + + # ------------------ + # helpers + # ------------------ + # find a the pe idx for a task + def get_tasks_pe_helper(mapping, task): + for idx, tasks in enumerate(mapping): + if task in tasks: + return idx + print("task is not found") + return -1 + + # filter mappings scenarios where source/sink don't map to the same pe as the second/second to last tasks respectively + # Since source and sink are dummies, it doesn't make a differrence if they are mapped to other pes + def filter_sink_source(all_taks_mappings, gen_config): + full_potential_tasks_list = gen_config["full_potential_tasks_list"] + MAX_TASK_CNT = gen_config["DB_MAX_TASK_CNT"] + + result = [] + for mapping in all_task_mappings: + source_idx = get_tasks_pe_helper(mapping, full_potential_tasks_list[0]) + task_1_idx = get_tasks_pe_helper(mapping, full_potential_tasks_list[1]) + if source_idx == -1 or task_1_idx == -1: + print("something went wrong") + exit(0) + if not (source_idx == task_1_idx): + continue + + sink_idx = get_tasks_pe_helper(mapping, full_potential_tasks_list[-1]) + last_task_idx = get_tasks_pe_helper(mapping, full_potential_tasks_list[-2]) + if sink_idx == -1 or last_task_idx == -1: + print("something went wrong") + exit(0) + if not (sink_idx == last_task_idx): + continue + + result.append(mapping) + + if len(result) == 0: # there are cases that because of the set up, the criteria can not be met + return all_taks_mappings + return result + + + mapped_systems = [] + start = time.time() + for idx, system in enumerate(filtered_system_list): + if len(mapped_systems) > DB_MAX_SYSTEM_to_investigate: + break + + all_task_mappings = list(binning(system.get_pe_cnt(), full_potential_tasks_list[0:MAX_TASK_CNT])) + + # Filtering + # flter 1: filter mappings scenarios where source/sink don't map to the same pe as the second/second to last tasks respectively + # Since source and sink are dummies, it doesn't make a differrence if they are mapped to other pes + #task_mapping_filtered_1 = all_task_mappings # uncomment if you don't care about source/sink + task_mapping_filtered_1 = filter_sink_source(all_task_mappings, gen_config) + # filter_2: filter the scenarios where tasks are mapped similarly to the same bus + #task_mapping_filtered_2 = remove_permutation_per_bus_2(task_mapping_filtered_1, system.get_pe_set()) + task_mapping_filtered_2 = task_mapping_filtered_1 + + use_balanced_mapping = True + # just to filter out certain scenarios for debugging. not necessary + if (use_balanced_mapping): + task_mapping_filtered_3 = [] + # find the most balanced design + task_mapping_mapping_std = {} + for idx, task_mapping in enumerate(task_mapping_filtered_2): + task_mapping_mapping_std[idx] = np.std([len(el) for el in task_mapping]) + sorted_x = collections.OrderedDict(sorted(task_mapping_mapping_std.items(), key=operator.itemgetter(1))) + + for idx in range(0, min(1, len(task_mapping_filtered_2))): + blah = task_mapping_filtered_2[list(sorted_x.keys())[idx]] + task_mapping_filtered_3.append(blah) + task_mapping_filtered_2 = task_mapping_filtered_3 + + # populate + for task_mapping in task_mapping_filtered_2: + system_ = deepcopy(system) + system_.set_pe_task_set(task_mapping) + mapped_systems.append(system_) + mapping_customization_variation_cnt_list.append((1, + pow(system_.get_pe_cnt(), DB_MAX_PE_CNT)* + pow(system_.get_mem_cnt(), DB_MAX_MEM_CNT)* + pow(system_.get_bus_cnt(), DB_MAX_BUS_CNT))) + + return mapped_systems,mapping_customization_variation_cnt_list + + +def exhaustive_system_generation(system_workers, gen_config): + mapping_process_cnt = system_workers[0] + mapping_process_id = system_workers[1] + FARSI_gen_process_id = system_workers[1] + #customization_process_cnt = system_workers[2] + #customization_process_id = system_workers[3] + + # mapping assisted topology + #task_cnt = MAX_TASK_CNT + system_variations = 0 + + # generate systems with various topologies + system_topologies = generate_topologies(gen_config) + + # count all the valid mappings and customization (together) + mapping_customization_variation_cnt_list, total_system_variation_cnt = count_mapping_customization(system_topologies, gen_config) + + # generate different mapping + start = time.time() + mapped_pe_systems, mapping_customization_variation_cnt_list = generate_pe_mapping(system_topologies, gen_config) # map tasks to PEs + print("pe mapping time" + str(time.time() - start)) + end = time.time() + #mapped_systems_completed = mapped_pe_systems + + # parallelize + process_id_work_dictionary = shard_work_equally(mapping_customization_variation_cnt_list, mapping_process_cnt) + + # map tasks to MEMs. comment out if you don't care about this + start = time.time() + mapped_systems_completed = generate_mem_mapping(mapped_pe_systems, process_id_work_dictionary, mapping_process_id) + print("mem mapping time" + str(time.time() - start)) + + # generate different customizations + start = time.time() + customized_systems = generate_customization(mapped_systems_completed, gen_config) + print("customization time" + str(time.time() - start)) + + print("total systems to explore for process id" + str(mapping_process_id) + "_" + str(FARSI_gen_process_id)+ "_" + str(len(customized_systems))) + return customized_systems[: min(len(customized_systems), gen_config["DB_MAX_SYSTEM_to_investigate"])] + + +#for DS_type in ["naive", "DB_semantics"]: +#--------------------------- +# sweepers +#--------------------------- +def sweep_DS_info(gen_config): + DB_MAX_PE_CNT = gen_config["DB_MAX_PE_CNT"] + DB_MAX_MEM_CNT = gen_config["DB_MAX_MEM_CNT"] + DB_MAX_BUS_CNT = gen_config["DB_MAX_BUS_CNT"] + MAX_PAR_TASK_CNT = gen_config["MAX_PAR_TASK_CNT"] + MAX_TASK_CNT = gen_config["MAX_TASK_CNT"] + DB_MAX_PE_CNT_range = gen_config["DB_MAX_PE_CNT_range"] + MAX_TASK_range = gen_config["MAX_TASK_CNT_range"] + MAX_TASK_CNT_range = gen_config["MAX_TASK_CNT_range"] + MAX_PAR_TASK_CNT_range = gen_config["MAX_PAR_TASK_CNT_range"] + DS_output_mode= gen_config["DS_output_mode"] + + DS_type = gen_config["DS_type"] + print("DS_type:" + DS_type) + for DB_MAX_PE_CNT in DB_MAX_PE_CNT_range: + + # printing stuff + print("-----------------------------------------") + print("PB DB count:" + str(DB_MAX_PE_CNT)) + print("-----------------------------------------") + print(" ,", end=" ") + for MAX_PAR_TASK_CNT in MAX_PAR_TASK_CNT_range: + print(str(MAX_PAR_TASK_CNT) +",", end =" ") + print("\n") + + DB_MAX_BUS_CNT = DB_MAX_MEM_CNT = DB_MAX_PE_CNT + for MAX_TASK_CNT in MAX_TASK_CNT_range: + print(str(MAX_TASK_CNT) +",", end=" ") + for MAX_PAR_TASK_CNT in MAX_PAR_TASK_CNT_range: + print(str(get_DS_cnt(DS_type, gen_config, DS_output_mode)) +",", end =" ") + print("\n") + +def lists_of_lists_equal(lol1, lol2): + if not (len(lol1) == len(lol2)): + return False + for lol1_el in lol1: + if not(lol1_el in lol2): + return False + return True + +def listify_tuples(list_): + result = [] + for el in list_: + if isinstance(el, tuple): + result.extend(list(el)) + else: + result.extend(el) + return result + +# mapping equal if the tasks under the PEs mapped to the same bus +# are equal +def mapping_equal(system_1, system_2, IP_set_per_bus): + if not len(system_1) == len(system_2): + return False + + IP_set_per_bus_acc = [0] + for idx, IP_set_per_bus_el in enumerate(IP_set_per_bus): + IP_set_per_bus_acc.append(IP_set_per_bus_acc[idx] + IP_set_per_bus_el) + + for idx in range(0, len(IP_set_per_bus_acc) - 1): + idx_low = IP_set_per_bus_acc[idx] + idx_up = IP_set_per_bus_acc[idx + 1] + #system_1_listified = listify_tuples(system_1) + #system_2_listified = listify_tuples(system_2) + #if not lists_of_lists_equal(system_1_listified[idx_low:idx_up], system_2_listified[idx_low:idx_up]): + if not lists_of_lists_equal(system_1[idx_low:idx_up], system_2[idx_low:idx_up]): + return False + + return True + +# since permutations of the blocks per buses do not generate new toopologies (since we haven't +# assigned a IP to them), we need to remove them +def remove_permutation_per_bus_2(PE_with_task_mapping_list, IP_set_per_bus): + + duplicated_systems_idx = [] + # iterate through and check the equality of each design + for idx_x in range(0, len(PE_with_task_mapping_list)): + if idx_x in PE_with_task_mapping_list: + continue + for idx_y in range(idx_x+1, len(PE_with_task_mapping_list)): + if mapping_equal(PE_with_task_mapping_list[idx_x], PE_with_task_mapping_list[idx_y], IP_set_per_bus): + duplicated_systems_idx.append(idx_y) + + non_duplicates = [PE_with_task_mapping_list[idx] for idx in range(0, len(PE_with_task_mapping_list)) + if not(idx in duplicated_systems_idx)] + + + return non_duplicates + +# ------------------ +# some unit test. keep for unit testing +# ------------------ +""" +system_1 = [[1,2,3], [4],[5]] +system_2 = [[1,3,2], [4]] +print(mapping_equal(system_1, system_2, [2,1]) == False) + +system_1 = [[1,2,3], [4,5], [5]] +system_2 = [[4, 5], [1,2,3], [5]] +print(mapping_equal(system_1, system_2, [2,1]) == True) + +system_1 = [[1,2,3], [4,5], [5], [6]] +system_2 = [[4, 5], [1,2,3], [6], [5]] +print(mapping_equal(system_1, system_2, [2,2]) == True) + +system_1 = [[1,2,3], [4,5], [5], [6]] +system_2 = [[4, 5], [1,2,3], [6], [5]] +print(mapping_equal(system_1, system_2, [3,1]) == False) + + +system_1 = [[1,2,3], [4,5], [5], [6]] +system_2 = [[1, 2,3], [5], [6], [4,5]] +print(mapping_equal(system_1, system_2, [1,3]) == True) + + +system_1 = [[1,2,3], [4,5], [5], [6]] +system_2 = [[2,3], [5], [6], [4,5]] +print(mapping_equal(system_1, system_2, [1,3]) == False) + + +system_1 = [[1,2,3], [4,5], [5], [6]] +system_2 = [[2,3, 1], [5], [6], [4,5]] +print(mapping_equal(system_1, system_2, [1,3]) == False) + +system_1 = [[1,2,3], [4,5]] +system_2 = [[4,5], [1,2,3]] +print(mapping_equal(system_1, system_2, [1,1]) == False) + +system_1 = [[1,2,3], [4,5]] +system_2 = [[4,5], [1,2,3]] +print(mapping_equal(system_1, system_2, [2,0]) == True) +print("ok") +""" +""" +# binning tasks to PEs +total_PEs = 3 +PE_with_task_mapping = list(binning(total_PEs, full_potential_tasks_list[:4])) +IP_set_per_bus = [1,1,1] +remove_permutation_per_bus_2(PE_with_task_mapping, IP_set_per_bus) +#a = bin.combinations() +print(results) +""" + +#exhaustive_system_generation() +""" +def gen_test(): + for i in range(0,10): + yield i + + return None + +gen = gen_test() +for _ in gen: + print(_) +""" +#for MAX_TASK_CNT in range(5, 8): +# for MAX_PAR_TASK_CNT in range(1,3): +# system_variation_count_2() + +# all the combinations of balls and bins +# # https://www.careerbless.com/aptitude/qa/permutations_combinations_imp7.php + +import matplotlib.pyplot as plt +plt.style.use('seaborn-whitegrid') +import numpy as np + + +def plot_design_space_size(): + pe_range = [10, 12, 14, 16, 18, 20] + pe_count_design_space_size = {} + knob_count = 4 + for pe_cnt in pe_range: + pe_count_design_space_size[pe_cnt] = count_system_variation(pe_cnt, knob_count, "customization") + + + #colors = {'Sim Time: Sub Sec':'lime', 'Sim Time: Sec':'darkgreen', 'Sim Time: Minute':'darkgreen', 'Sim Time: Hour':'olivedrab', 'Sim Time: Day':'darkgreen'} + colors = {'Sim Time: Sub Sec':'lime', 'Sim Time: Sec':'darkgreen', 'Sim Time: Minute':'darkgreen', 'Sim Time: Hour':'white', 'Sim Time: Day':'white'} + simulation_time = {"Sim Time: Sub Sec": .1*1/60, "Sim Time: Sec": 1/60, "Sim Time: Minute":2, 'Sim Time: hour': 60, 'Sim Time: Day': 60*24} + #simulation_time = {'mili-sec':.001*1/60, 'hour': 60, 'day': 60*24} + selected_simulation_time = {'Sim Time: Hour': 60, 'Sim Time: Day': 60*24} + + + # budget + + font_size =25 + + # cardinality + fig, ax1 = plt.subplots() + #ax1 = ax3.twinx() + ax1.set_xlabel('Number of Processing Elements', fontsize=font_size) + ax1.set_ylabel('Design Space Size', fontsize =font_size) + ax1.plot(list(pe_count_design_space_size.keys()), list(pe_count_design_space_size.values()), label="Cardinality", color='red') + #plt.legend() #loc="upper left") + + + # exploration time + ax2 = ax1.twinx() + cntr = 0 + for k ,v in selected_simulation_time.items(): + ax2.plot(list(pe_count_design_space_size.keys()), + [(el * v)/ (365 * 24 * 60) for el in list(pe_count_design_space_size.values())], label=k, color=colors[k], linestyle=":") + cntr +=1 + + + """ + k = 'Sim Time: Sub Sec' + v = simulation_time[k] + percentage = .0001 + ax2.plot(list(pe_count_design_space_size.keys()), + [(el * v*percentage) / (30 * 24 * 60) for el in list(pe_count_design_space_size.values())], label=k + "+ selected points", color="gold", + linestyle=":") + """ + + #ax2.plot(list(pe_count_design_space_size.keys()), + # [(el * v) / (30 * 24 * 60) for el in list(pe_count_design_space_size.values())], label=k + " + selected points", color="yellow", linestyle=":") + ax2.set_ylabel('Exploration Time (Year)', fontsize=font_size) + + ax3 = ax1.twinx() + #ax3.hlines(y=100, color='blue', linestyle='-', xmin=min(pe_range), xmax=20, label="Exploration Time Budget") + ax3.hlines(y=100, color='white', linestyle='-', xmin=min(pe_range), xmax=20, label="Exploration Time Budget") + + #ax1.hlines(y=.00000005, color='r', linestyle='-', xmin=8, xmax=20) + + + + # ticks and such + ax1.tick_params(axis='y', labelsize=font_size) + ax1.tick_params(axis='x', labelsize=font_size) + + #ax2.tick_params(axis='y', which="both", bottom=False, top=False, right=False, left=False, labelbottom=False) + #ax3.tick_params(axis='y', which="both", bottom=False, top=False, right=False, left=False, labelbottom=False) + #ax3.tick_params(None) + ax1.set_yscale('log') + ax2.set_yscale('log') + ax3.set_yscale('log') + + ax1.set_xticklabels([10, 12, 15, 17, 20]) + ax2.set_yticklabels([]) + ax3.set_yticklabels([]) + + ax2.xaxis.set_ticks_position('none') + ax2.yaxis.set_ticks_position('none') + ax3.xaxis.set_ticks_position('none') + ax3.yaxis.set_ticks_position('none') + + ax1.set_ylim((1, ax1.get_ybound()[1])) + ax2.set_ylim((1, ax1.get_ybound()[1])) + ax3.set_ylim((1, ax1.get_ybound()[1])) + ax1.set_xlim((10, 20)) + ax2.set_xlim((10, 20)) + ax3.set_xlim((10, 20)) + + + + + + # show + #plt.legend() #loc="upper left") + fig.tight_layout() + plt.show() + fig.savefig("exploration_time.png") + + print("ok") +# print("number of system variations: " + str(float(system_variations))) +# print("exhaustive simulation time (hours): " + str(float(system_variations)/(20*3600))) + +def count_system_variation(task_cnt, knob_count, mode="all"): + MAX_TASK_CNT = task_cnt + + # assuming that we have 5 different tasks and hence (can have up to 5 different blocks). + # we'd like to know how many different migration/allocation combinations are out there. + # Assumptions: + # PE's are identical. + # Buses are identical + # memory is ignored for now + + MAX_PE_CNT = MAX_TASK_CNT + task_cnt = MAX_TASK_CNT + system_variations = 0 + num_of_knobs = knob_count + + topology_dict = [1, + 1, + 4, + 38, + 728, + 26704, + 1866256, + 251548592, + 66296291072, + 34496488594816, + 35641657548953344, + 73354596206766622208, + 301272202649664088951808, + 2471648811030443735290891264, + 40527680937730480234609755344896, + 1328578958335783201008338986845427712, + 87089689052447182841791388989051400978432, + 11416413520434522308788674285713247919244640256, + 2992938411601818037370034280152893935458466172698624, + 1569215570739406346256547210377768575765884983264804405248, + 1645471602537064877722485517800176164374001516327306287561310208] + + for pe_cnt in range(1, MAX_PE_CNT): + + topology = topology_dict[pe_cnt-1] + # then calculate mapping (migrate) + mapping = calc_stirling(task_cnt, pe_cnt)*factorial(pe_cnt) + # then calculate customization (swap) + swap = math.pow(num_of_knobs, (pe_cnt)) + + if mode == "all": + system_variations += topology*mapping*swap + if mode == "customization": + system_variations += swap + if mode == "mapping": + system_variations += mapping + if mode == "topology": + system_variations += topology + + + + + + return system_variations + #print("{:e}".format(system_variations)) + +pe_cnt = 20 +knob_count = 4 +ds_size = {} +ds_size_digits = {} +for design_stage in ["topology", "mapping", "customization", "all"]: + ds_size[design_stage] = count_system_variation(pe_cnt, knob_count, design_stage) + ds_size_digits[design_stage] = math.log10(count_system_variation(pe_cnt, knob_count, design_stage)) + + + +#plot_design_space_size() + diff --git a/Project_FARSI/DSE_utils/hill_climbing.py b/Project_FARSI/DSE_utils/hill_climbing.py new file mode 100644 index 00000000..7215bbb5 --- /dev/null +++ b/Project_FARSI/DSE_utils/hill_climbing.py @@ -0,0 +1,3870 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. +from copy import * +from decimal import Decimal +import zipfile +import csv +import _pickle as cPickle +#import ujson +from design_utils.components.hardware import * +from design_utils.components.workload import * +from design_utils.components.mapping import * +from design_utils.components.scheduling import * +from SIM_utils.SIM import * +from design_utils.design import * +from design_utils.des_handler import * +from design_utils.components.krnel import * +from typing import Dict, Tuple, List +from settings import config +from visualization_utils import vis_hardware, vis_stats, plot +from visualization_utils import vis_sim +#from data_collection.FB_private.verification_utils.common import * +import dill +import pickle +import importlib +import gc +import difflib +#from pygmo import * +#from pygmo.util import * +import psutil + + +class Counters(): + def __init__(self): + self.krnel_rnk_to_consider = 0 + self.krnel_stagnation_ctr = 0 + self.fitted_budget_ctr = 0 + self.des_stag_ctr = 0 + self.krnels_not_to_consider = [] + self.population_generation_cnt = 0 + self.found_any_improvement = False + self.total_iteration_ctr = 0 + + def reset(self): + self.krnel_rnk_to_consider = 0 + self.krnel_stagnation_ctr = 0 + self.fitted_budget_ctr = 0 + self.des_stag_ctr = 0 + self.krnels_not_to_consider = [] + self.population_generation_cnt = 0 + self.found_any_improvement = False + + + def update(self, krnel_rnk_to_consider, krnel_stagnation_ctr, fitted_budget_ctr, des_stag_ctr, krnels_not_to_consider, population_generation_cnt, found_any_improvement, total_iteration_ctr): + self.krnel_rnk_to_consider = krnel_rnk_to_consider + self.krnel_stagnation_ctr = krnel_stagnation_ctr + self.fitted_budget_ctr = fitted_budget_ctr + self.des_stag_ctr = des_stag_ctr + self.krnels_not_to_consider = krnels_not_to_consider[:] + self.population_generation_cnt = population_generation_cnt + self.found_any_improvement = found_any_improvement + self.total_iteration_ctr = total_iteration_ctr + #def update_improvement(self, improvement): + # self.found_any_improvement = self.found_any_improvement or improvement + + + # ------------------------------ +# This class is responsible for design space exploration using our proprietary hill-climbing algorithm. +# Our Algorithm currently uses swap (improving the current design) and duplicate (relaxing the contention on the +# current bottleneck) as two main exploration move. +# ------------------------------ +class HillClimbing: + def __init__(self, database, result_dir): + + # parameters (to configure) + self.counters = Counters() + self.found_any_improvement = False + self.result_dir = result_dir + self.fitted_budget_ctr = 0 # counting the number of times that we were able to find a design to fit the budget. Used to terminate the search + self.name_ctr = 0 + self.DES_STAG_THRESHOLD = config.DES_STAG_THRESHOLD # Acceptable iterations count without improvement before termination. + self.TOTAL_RUN_THRESHOLD = config.TOTAL_RUN_THRESHOLD # Total number of iterations to terminate with. + self.neigh_gen_mode = config.neigh_gen_mode # Neighbouring design pts generation mode ("all" "random_one"). + self.num_neighs_to_try = config.num_neighs_to_try # How many neighs to try around a current design point. + self.neigh_sel_mode = config.neigh_sel_mode # Neighbouring design selection mode (best, sometimes, best ...) + self.dp_rank_obj = config.dp_rank_obj # Design point ranking object function(best, sometimes, best ...) + self.num_clusters = config.num_clusters # How many clusters to create everytime we split. + self.budget_coeff = config.max_budget_coeff + self.move_profile = [] + # variables (to initialize) + self.area_explored = [] # List containing area associated with the all explored designs areas. + self.latency_explored = [] # List containing latency associated with all explored designs latency. + self.power_explored = [] # List containing Power associated with all explored designs latency. + self.design_itr = [] # Design iteration counter. Simply indexing (numbering) the designs explored. + self.space_distance = 2 + self.database = database # hw/sw database to use for exploration. + self.dh = DesignHandler(self.database) # design handler for design modification. + self.so_far_best_sim_dp = None # best design found so far through out all iterations. + # For iteratively improvements. + self.cur_best_ex_dp, self.cur_best_sim_dp = None, None # current iteration's best design. + self.last_des_trail = None # last design (trail) + self.last_move = None # last move applied + self.init_ex_dp = None # Initial exploration design point. (Staring design point for the whole algorithm) + self.coeff_slice_size = int(self.TOTAL_RUN_THRESHOLD/config.max_budget_coeff) + #self.hot_krnl_pos = 0 # position of the kernel among the kernel list. Used to found and improve the + # corresponding occupying block. + + self.min_cost_to_consider = .000001 + # Counters: to determine which control path the exploration should take (e.g., terminate, pick another block instead + # of hotblock, ...). + self.des_stag_ctr = 0 # Iteration count since seen last design improvement. + self.population_generation_cnt = 0 # Total iteration count (for termination purposes). + + self.vis_move_trail_ctr = 0 + # Sanity checks (preventing bad configuration setup) + if self.neigh_gen_mode not in ["all", "some"]: raise ValueError() + # TODO: sel_cri needs to be fixed to include combinations of the objective functions + if self.dp_rank_obj not in ["all", "latency", "throughput", "power", "design_cost"]: raise ValueError() + if self.neigh_sel_mode not in ["best", "best_sometime"]: raise ValueError() + self.des_trail_list = [] + self.krnel_rnk_to_consider = 0 # this rank determines which kernel (among the sorted kernels to consider). + # we use this counter to avoid getting stuck + self.krnel_stagnation_ctr = 0 # if the same kernel is selected across iterations and no improvement observed, + # count up + + self.recently_seen_design_ctr = 0 + self.recently_cached_designs = {} + self.cleanup_ctr = 0 # use this to invoke the cleaner once we pass a threshold + + self.SA_current_breadth = -1 # which breadth is current move on + self.SA_current_mini_breadth = 0 # which breadth is current move on + self.SA_current_depth = -1 # which depth is current move on + self.check_point_folder = config.check_point_folder + + self.seen_SOC_design_codes = [] # config code of all the designs seen so far (this is mainly for debugging, concretely + # simulation validation + + self.cached_SOC_sim = {} # cache of designs simulated already. index is a unique code base on allocation and mapping + + self.move_s_krnel_selection = config.move_s_krnel_selection + self.krnels_not_to_consider = [] + self.all_itr_ex_sim_dp_dict: Dict[ExDesignPoint: SimDesignPoint] = {} # all the designs look at + self.reason_to_terminate = "" + self.log_data_list = [] + self.population_observed_ctr = 0 # + self.neighbour_selection_time = 0 + self.total_iteration_cnt = 0 + self.total_iteration_ctr = 0 + self.moos_tree = moosTreeModel(config.budgetted_metrics) # only used for moos heuristic + self.ctr_l = 0 + + def get_total_iteration_cnt(self): + return self.total_iteration_cnt + + def set_check_point_folder(self, check_point_folder): + self.check_point_folder = check_point_folder + + # retrieving the pickled check pointed file + def get_pickeld_file(self, file_addr): + if not os.path.exists(file_addr): + file_name = os.path.basename(file_addr) + file_name_modified = file_name.split(".")[0]+".zip" + dir_name = os.path.dirname(file_addr) + zip_file_addr = os.path.join(dir_name, file_name_modified) + if os.path.exists(zip_file_addr): + with zipfile.ZipFile(zip_file_addr) as thezip: + with thezip.open(file_name, mode='r') as f: + obj = pickle.load(f) + else: + print(file_addr +" does not exist for unpickling") + exit(0) + else: + with open(file_addr, 'rb') as f: # will close() when we leave this block + obj = pickle.load(f) + return obj + + def populate_counters(self, counters): + self.counters = counters + self.krnel_rnk_to_consider = counters.krnel_rnk_to_consider + self.krnel_stagnation_ctr= counters.krnel_stagnation_ctr + self.fitted_budget_ctr = counters.fitted_budget_ctr + self.des_stag_ctr = counters.des_stag_ctr + self.krnels_not_to_consider = counters.krnels_not_to_consider[:] + self.population_generation_cnt = counters.population_generation_cnt + self.found_any_improvement = counters.found_any_improvement + self.total_iteration_ctr = counters.total_iteration_ctr + + # ------------------------------ + # Functionality + # generate initial design point to start the exploration from. + # If mode is from_scratch, the default behavior is to pick the cheapest design. + # If mode is check_pointed, we start from a previously check pointed design. + # If mode is hardcode, we pick a design that is hardcoded. + # Variables + # init_des_point: initial design point + # mode: starting point mode (from scratch or from check point) + # ------------------------------ + def gen_init_ex_dp(self, mode="generated_from_scratch", init_des=""): + if mode == "generated_from_scratch": # start from the simplest design possible + self.init_ex_dp = self.dh.gen_init_des() + elif mode == "generated_from_check_point": + pickled_file_addr = self.check_point_folder + "/" + "ex_dp_pickled.txt" + database_file_addr = self.check_point_folder + "/" + "database_pickled.txt" + counters_file_addr = self.check_point_folder + "/" + "counters_pickled.txt" + #sim_pickled_file_addr = self.check_point_folder + "/" + "sim_dp_pickled.txt" + if "db" in config.check_point_list: + self.database = self.get_pickeld_file(database_file_addr) + if "counters" in config.check_point_list: + self.counters = self.get_pickeld_file(counters_file_addr) + self.init_ex_dp = self.get_pickeld_file(pickled_file_addr) + self.populate_counters(self.counters) + elif mode == "FARSI_des_passed_in": + self.init_ex_dp = init_des + elif mode == "hardcoded": + self.init_ex_dp = self.dh.gen_specific_hardcoded_ex_dp(self.dh.database) + elif mode == "parse": + self.init_ex_dp = self.dh.gen_specific_parsed_ex_dp(self.dh.database) + elif mode == "hop_mode": + self.init_ex_dp = self.dh.gen_specific_design_with_hops_and_stars(self.dh.database) + elif mode == "star_mode": + self.init_ex_dp = self.dh.gen_specific_design_with_a_star_noc(self.dh.database) + else: raise Exception("mode:" + mode + " is not supported") + + + # ------------------------------ + # Functionality: + # Generate one neighbouring design based on the moves available. + # To do this, we first specify a move and then apply it. + # A move is specified by a metric, direction kernel, block, and transformation. + # look for move definition in the move class + # Variables + # des_tup: design tuple. Contains a design tuple (ex_dp, sim_dp). ex_dp: design to find neighbours for. + # sim_dp: simulated ex_dp. + # ------------------------------ + def gen_one_neigh(self, des_tup): + ex_dp, sim_dp = des_tup + + # Copy to avoid modifying the current designs. + #new_ex_dp_pre_mod = copy.deepcopy(ex_dp) # getting a copy before modifying + #new_sim_dp_pre_mod = copy.deepcopy(sim_dp) # getting a copy before modifying + #new_ex_dp = copy.deepcopy(ex_dp) + t1 = time.time() + gc.disable() + new_ex_dp = cPickle.loads(cPickle.dumps(ex_dp, -1)) + gc.enable() + t2 = time.time() + #new_sim_dp = copy.deepcopy(sim_dp) + new_des_tup = (new_ex_dp, sim_dp) + + # ------------------------ + # select (generate) a move + # ------------------------ + # It's important that we do analysis of move selection on the copy (and not the original) because + # 1. we'd like to keep original for further modifications + # 2. for block identification/comparison of the move and the copied design + safety_chk_passed = False + # iterate and continuously generate moves, until one passes some sanity check + while not safety_chk_passed: + move_to_try, total_transformation_cnt = self.sel_moves(new_des_tup, "dist_rank") + safety_chk_passed = move_to_try.safety_check(new_ex_dp) + move_to_try.populate_system_improvement_log() + + move_to_try.set_logs(t2-t1, "pickling_time") + + # ------------------------ + # apply the move + # ------------------------ + # while conduction various validity/sanity checks + try: + self.dh.unload_read_mem(new_des_tup[0]) # unload read memories + move_to_try.validity_check() # call after unload rad mems, because we need to check the scenarios where + # task is unloaded from the mem, but was decided to be migrated/swapped + new_ex_dp_res, succeeded = self.dh.apply_move(new_des_tup, move_to_try) + move_to_try.set_before_after_designs(new_des_tup[0], new_ex_dp_res) + new_ex_dp_res.sanity_check() # sanity check + move_to_try.sanity_check() + self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp_res) # loading the tasks on to memory and ic + new_ex_dp_res.hardware_graph.pipe_design() + new_ex_dp_res.sanity_check() + except Exception as e: + # if the error is already something that we are familiar with + # react appropriately, otherwise, simply raise it. + if e.__class__.__name__ in errors_names: + print("Error: " + e.__class__.__name__) + # TODOs + # for now, just return the previous design, but this needs to be fixed immediately + new_ex_dp_res = ex_dp + #raise e + elif e.__class__.__name__ in exception_names: + print("Exception: " + e.__class__.__name__) + new_ex_dp_res = ex_dp + move_to_try.set_validity(False) + else: + raise e + + + return new_ex_dp_res, move_to_try, total_transformation_cnt + + # ------------------------------ + # Functionality: + # Select a item given a probability distribution. + # The input provides a list of values and their probabilities/fitness,... + # and this function randomly picks a value based on the fitness/probability, ... + # Used for random but prioritized selections (of for example blocks, or kernels) + # input: item_prob_dict {} (item, probability) + # ------------------------------ + def pick_from_prob_dict(self, item_prob_dict): + # now encode this priorities into a encoded histogram (for example with performance + # encoded as 1 and power as 2 and ...) with frequencies + if config.DEBUG_FIX: random.seed(0) + else: time.sleep(.00001), random.seed(datetime.now().microsecond) + + item_encoding = np.arange(0, len(item_prob_dict.keys())) # encoding the metrics from 1 to ... clusters + rand_var_dis = list(item_prob_dict.values()) # distribution + + encoded_metric = np.random.choice(item_encoding, p=rand_var_dis) # cluster (metric) selected + selected_item = list(item_prob_dict.keys())[encoded_metric] + return selected_item + + # ------------------------------ + # Functionality: + # return all the hardware blocks that have same hardware characteristics (e.g, same type and same work-rate and mappability) + # ------------------------------ + def find_matching_blocks(self, blocks): + matched_idx = [] + matching_blocks = [] + for idx, _ in enumerate(blocks): + if idx in matched_idx: + continue + matched_idx.append(idx) + for idx_2 in range(idx+1, len(blocks)): + if blocks[idx].get_generic_instance_name() == blocks[idx_2].get_generic_instance_name(): # for PEs + matching_blocks.append((blocks[idx], blocks[idx_2])) + matched_idx.append(idx_2) + elif blocks[idx].type in ["mem", "ic"] and blocks[idx_2].type in ["mem", "ic"]: # for mem and ic + if blocks[idx].subtype == blocks[idx_2].subtype: + matching_blocks.append((blocks[idx], blocks[idx_2])) + matched_idx.append(idx_2) + return matching_blocks + + def get_task_parallelism_type(self, sim_dp, task, parallel_task): + workload_tasks = sim_dp.database.db_input.workload_tasks + task_s_workload = sim_dp.database.db_input.task_workload[task] + parallel_task_s_workload = sim_dp.database.db_input.task_workload[parallel_task] + if task_s_workload == parallel_task_s_workload: + return "task_level_parallelism" + else: + return "workload_level_parallelism" + + # check if there is another task (on the block that can run in parallel with the task of interest + def check_if_task_can_run_with_any_other_task_in_parallel(self, sim_dp, task, block): + parallelism_type = [] + if task.get_name() in ["souurce", "siink", "dummy_last"]: + return False, parallelism_type + if block.type == "pe": + task_dir = "loop_back" + else: + task_dir = "write" + tasks_of_block = [task_ for task_ in block.get_tasks_of_block_by_dir(task_dir) if + (not ("souurce" in task_.name) or not ("siink" in task_.name))] + if config.parallelism_analysis == "static": + for task_ in tasks_of_block: + if sim_dp.get_dp_rep().get_hardware_graph().get_task_graph().tasks_can_run_in_parallel(task_, task): + return True, _ + elif config.parallelism_analysis == "dynamic": + parallel_tasks_names_ = sim_dp.get_dp_rep().get_tasks_parallel_task_dynamically(task) + tasks_using_diff_pipe_cluster = sim_dp.get_dp_rep().get_tasks_using_the_different_pipe_cluster(task, block) + parallel_tasks_names= list(set(parallel_tasks_names_) - set(tasks_using_diff_pipe_cluster)) + for task_ in tasks_of_block: + if task_.get_name() in parallel_tasks_names: + parallelism_type.append(self.get_task_parallelism_type(sim_dp, task.get_name(), task_.get_name())) + if len(parallelism_type) > 0: + return True, list(set(parallelism_type)) + return False, parallelism_type + + + # ------------------------------ + # Functionality: + # check if there are any tasks across two blocks that can be run in parallel + # this is used for cleaning up, if there is not opportunities for parallelization + # Variables: + # sim_dp: design + # ------------------------------ + def check_if_any_tasks_on_two_blocks_parallel(self, sim_dp, block_1, block_2): + tasks_of_block_1 = [task for task in block_1.get_tasks_of_block_by_dir("write") if not task.is_task_dummy()] + tasks_of_block_2 = [task for task in block_2.get_tasks_of_block_by_dir("write") if not task.is_task_dummy()] + + for idx_1, _ in enumerate(tasks_of_block_1): + tsk_1 = tasks_of_block_1[idx_1] + parallel_tasks_names_ = sim_dp.get_dp_rep().get_tasks_parallel_task_dynamically(tsk_1) + tasks_using_diff_pipe_cluster = sim_dp.get_dp_rep().get_tasks_using_the_different_pipe_cluster(tsk_1, block_1) + parallel_tasks_names = list(set(parallel_tasks_names_) - set(tasks_using_diff_pipe_cluster)) + + + for idx_2, _ in enumerate(tasks_of_block_2): + tsk_2 = tasks_of_block_2[idx_2] + if tsk_1.get_name() == tsk_2.get_name(): + continue + if config.parallelism_analysis == "static": + if sim_dp.get_dp_rep().get_hardware_graph().get_task_graph().tasks_can_run_in_parallel(tsk_1, tsk_2): + return True + elif config.parallelism_analysis == "dynamic": + if tsk_2.get_name() in parallel_tasks_names: + return True + return False + + # ------------------------------ + # Functionality: + # Return all the blocks that are unnecessarily parallelized, i.e., there are no + # tasks across them that can run in parallel. + # this is used for cleaning up, if there is not opportunities for parallelization + # Variables: + # sim_dp: design + # matching_blocks_list: list of hardware blocks with equivalent characteristics (e.g., two A53, or two + # identical acclerators) + # ------------------------------ + def find_blocks_with_all_serial_tasks(self, sim_dp, matching_blocks_list): + matching_blocks_list_filtered = [] + for matching_blocks in matching_blocks_list: + if self.check_if_any_tasks_on_two_blocks_parallel(sim_dp, matching_blocks[0], matching_blocks[1]): + continue + matching_blocks_list_filtered.append((matching_blocks[0], matching_blocks[1])) + + return matching_blocks_list_filtered + + # ------------------------------ + # Functionality: + # search through all the blocks and return a pair of blocks that cleanup can apply to + # Variables: + # sim_dp: design + # ------------------------------ + def pick_block_pair_to_clean_up(self, sim_dp, block_pairs): + if len(block_pairs) == 0: + return block_pairs + + cleanup_ease_list = [] + block_pairs_sorted = [] # sorting the pairs elements (within each pair) based on the number of tasks on each + for blck_1, blck_2 in block_pairs: + if blck_2.type == "ic": # for now ignore ics + continue + elif blck_2.type == "mem": + if self.database.check_superiority(blck_1, blck_2): + block_pairs_sorted.append((blck_1, blck_2)) + else: + block_pairs_sorted.append((blck_2, blck_1)) + else: + if len(blck_1.get_tasks_of_block()) < len(blck_2.get_tasks_of_block()): + block_pairs_sorted.append((blck_1, blck_2)) + else: + block_pairs_sorted.append((blck_2, blck_1)) + + distance = len(sim_dp.get_dp_rep().get_hardware_graph().get_path_between_two_vertecies(blck_1, blck_2)) + num_tasks_to_move = min(len(blck_1.get_tasks_of_block()), len(blck_2.get_tasks_of_block())) + + cleanup_ease_list.append(distance + num_tasks_to_move) + + # when we need to clean up the ics, ignore for now + if len(cleanup_ease_list) == 0: + return [] + + picked_easiest = False + min_ease = 100000000 + for idx, ease in enumerate(cleanup_ease_list): + if ease < min_ease: + picked_easiest = True + easiest_pair = block_pairs_sorted[idx] + min_ease = ease + return easiest_pair + + # ------------------------------ + # Functionality: + # used to determine if two different task can use the same accelerators. + # ------------------------------ + def are_same_ip_tasks(self, task_1, task_2): + return (task_1.name, task_2.name) in self.database.db_input.misc_data["same_ip_tasks_list"] or (task_2.name, task_1.name) in self.database.db_input.misc_data["same_ip_tasks_list"] + + # ------------------------------ + # Functionality: + # find all the tasks that can run on the same ip (accelerator) + # Variables: + # des_tup: (design, simulated design) + # ------------------------------ + def find_task_with_similar_mappable_ips(self, des_tup): + ex_dp, sim_dp = des_tup + #krnls = sim_dp.get_dp_stats().get_kernels() + blcks = ex_dp.get_blocks() + pe_blocks = [blck for blck in blcks if blck.type=="pe"] + tasks_sub_ip_type = [] # (task, sub_ip) + matches = [] + for blck in pe_blocks: + tasks_sub_ip_type.extend(zip(blck.get_tasks_of_block(), [blck.subtype]*len(blck.get_tasks_of_block()))) + + for task, sub_ip_type in tasks_sub_ip_type: + check_for_similarity = False + if sub_ip_type == "ip": + check_for_similarity = True + if not check_for_similarity: + continue + + for task_2, sub_ip_type_2 in tasks_sub_ip_type: + if task_2.name == task.name : + continue + if self.are_same_ip_tasks(task, task_2): + for blck in pe_blocks: + if task_2 in blck.get_tasks_of_block(): + block_to_migrate_from = blck + if task in blck.get_tasks_of_block(): + block_to_migrate_to = blck + + if not (block_to_migrate_to == block_to_migrate_from): + matches.append((task_2, block_to_migrate_from, task, block_to_migrate_to)) + + if len(matches) == 0: + return None, None, None, None + else: + return random.choice(matches) + + # ------------------------------ + # Functionality: + # pick a block pair to apply cleaning to + # Variables: + # des_tup: (design, simulated design) + # ------------------------------ + def gen_block_match_cleanup_move(self, des_tup): + ex_dp, sim_dp = des_tup + krnls = sim_dp.get_dp_stats().get_kernels() + blcks = ex_dp.get_blocks() + + # move tasks to already generated IPs + # clean up the matching blocks + matching_blocks_list = self.find_matching_blocks(blcks) + matching_blocks_lists_filtered = self.find_blocks_with_all_serial_tasks(sim_dp, matching_blocks_list) + return self.pick_block_pair_to_clean_up(sim_dp, matching_blocks_lists_filtered) + + # ------------------------------ + # Functionality: + # is current iteration a clean up iteration (ie., should be used for clean up) + # ------------------------------ + def is_cleanup_iter(self): + result = (self.cleanup_ctr % (config.cleaning_threshold)) >= (config.cleaning_threshold- config.cleaning_consecutive_iterations) + return result + + def get_block_attr(self, selected_metric): + if selected_metric == "latency": + selected_metric_to_sort = 'peak_work_rate' + elif selected_metric == "power": + #selected_metric_to_sort = 'work_over_energy' + selected_metric_to_sort = 'one_over_power' + elif selected_metric == "area": + selected_metric_to_sort = 'one_over_area' + else: + print("selected_selected_metric: " + selected_metric + " is not defined") + return selected_metric_to_sort + + def select_block_to_migrate_to(self, ex_dp, sim_dp, hot_blck_synced, selected_metric, sorted_metric_dir, selected_krnl): + # get initial information + locality_type = [] + parallelism_type =[] + task = ex_dp.get_hardware_graph().get_task_graph().get_task_by_name(selected_krnl.get_task_name()) + selected_metric = list(sorted_metric_dir.keys())[-1] + selected_dir = sorted_metric_dir[selected_metric] + # find blocks equal or immeidately better + equal_imm_blocks_present_for_migration = self.dh.get_equal_immediate_blocks_present(ex_dp, hot_blck_synced, + selected_metric, selected_dir, [task]) + + + # does parallelism exist in the current occupying block + current_block_parallelism_exist, parallelism_type = self.check_if_task_can_run_with_any_other_task_in_parallel(sim_dp, + task, + hot_blck_synced) + inequality_dir = selected_dir*-1 + results_block = [] # results + + task_s_blocks = ex_dp.get_hardware_graph().get_blocks_of_task(task) + if len(task_s_blocks) == 0: + print("a task must have at lease three blocks") + exit(0) + + + remove_list = [] # list of blocks to remove from equal_imm_blocks_present_for_migration + # improve locality by only allowing migration to the PE/MEM close by + if hot_blck_synced.type == "mem": + # only keep memories that are connected to the IC neighbour of the task's pe + # This is to make sure that we keep data local (to the router), instead of migrating to somewhere far + task_s_pe = [blk for blk in task_s_blocks if blk.type == "pe"][0] # get task's pe + tasks_s_ic = [el for el in task_s_pe.get_neighs() if el.type == "ic"][0] # get pe's ic + potential_mems = [el for el in tasks_s_ic.get_neighs() if el.type == "mem"] # get ic's memories + for el in equal_imm_blocks_present_for_migration: + if el not in potential_mems: + remove_list.append(el) + locality_type = ["spatial_locality"] + for el in remove_list: + equal_imm_blocks_present_for_migration.remove(el) + elif hot_blck_synced.type == "pe": + # only keep memories that are connected to the IC neighbour of the task's pe + # This is to make sure that we keep data local (to the router), instead of migrating to somewhere far + task_s_mems = [blk for blk in task_s_blocks if blk.type == "mem"] # get task's pe + potential_pes = [] + for task_s_mem in task_s_mems: + tasks_s_ic = [el for el in task_s_mem.get_neighs() if el.type == "ic"][0] # get pe's ic + potential_pes.extend([el for el in tasks_s_ic.get_neighs() if el.type == "pe"]) # get ic's memories + for el in equal_imm_blocks_present_for_migration: + if el not in potential_pes: + remove_list.append(el) + locality_type = ["spatial_locality"] + for el in remove_list: + equal_imm_blocks_present_for_migration.remove(el) + + # iterate through the blocks and find the best one + for block_to_migrate_to in equal_imm_blocks_present_for_migration: + # skip yourself + if block_to_migrate_to == hot_blck_synced: + continue + + block_metric_attr = self.get_block_attr(selected_metric) # metric to pay attention to + # iterate and found blocks that are at least as good as the current block + if getattr(block_to_migrate_to, block_metric_attr) == getattr(hot_blck_synced, block_metric_attr): + # blocks have similar attr value + if (selected_metric == "power" and selected_dir == -1) or \ + (selected_metric == "latency" and selected_dir == 1) or (selected_metric == "area"): + # if we want to slow down (reduce latency, improve power), look for parallel task on the other block + block_to_mig_to_parallelism_exist, parallelism_type = self.check_if_task_can_run_with_any_other_task_in_parallel(sim_dp, + task, + block_to_migrate_to) + if (selected_metric == "area" and selected_dir == -1): + # no parallelism possibly allows for theo the other memory to shrink + if not block_to_mig_to_parallelism_exist: + results_block.append(block_to_migrate_to) + parallelism_type = ["serialism"] + else: + if block_to_mig_to_parallelism_exist: + results_block.append(block_to_migrate_to) + parallelism_type = ["serialism"] + else: + # if we want to accelerate (improve latency, get more power), look for parallel task on the same block + if current_block_parallelism_exist: + results_block.append(block_to_migrate_to) + elif inequality_dir*getattr(block_to_migrate_to, block_metric_attr) > inequality_dir*getattr(hot_blck_synced, block_metric_attr): + results_block.append(block_to_migrate_to) + break + + # if no block found, just load the results_block with current block + if len(results_block) == 0: + results_block = [hot_blck_synced] + found_block_to_mig_to = False + else: + found_block_to_mig_to = True + + # pick at random to try random scenarios. At the moment, only equal and immeidately better blocks are considered + random.seed(datetime.now().microsecond) + result_block = random.choice(results_block) + + selection_mode = "batch" + if found_block_to_mig_to: + if getattr(result_block, block_metric_attr) == getattr(hot_blck_synced, block_metric_attr): + selection_mode = "batch" + else: + selection_mode = "single" + + + return result_block, found_block_to_mig_to, selection_mode, parallelism_type, locality_type + + + def is_system_ic(self, ex_dp, sim_dp, blck): + if not sim_dp.dp_stats.fits_budget(1): + return False + elif sim_dp.dp_stats.fits_budget(1) and not self.dram_feasibility_check_pass(ex_dp): + return False + else: + for block in ex_dp.get_hardware_graph().get_blocks(): + neighs = block.get_neighs() + if any(el for el in neighs if el.subtype == "dram"): + if block == blck: + return True + return False + + def bus_has_pe_mem_topology_for_split(self, ex_dp, sim_dp, ref_task, block): + if not block.type == "ic" or ref_task.is_task_dummy(): + return False + found_pe_block = False + found_mem_block = False + + migrant_tasks = self.dh.find_parallel_tasks_of_task_in_block(ex_dp, sim_dp, ref_task, block)[0] + migrant_tasks_names = [el.get_name() for el in migrant_tasks] + mem_neighs = [el for el in block.get_neighs() if el.type == "mem"] + pe_neighs = [el for el in block.get_neighs() if el.type == "pe"] + + for neigh in pe_neighs: + neigh_tasks = [el.get_name() for el in neigh.get_tasks_of_block_by_dir("loop_back")] + # if no overlap skip + if len(list(set(migrant_tasks_names) - set(neigh_tasks) )) == len(migrant_tasks_names): + continue + else: + found_pe_block = True + break + + for neigh in mem_neighs: + neigh_tasks = [el.get_name() for el in neigh.get_tasks_of_block_by_dir("write")] + # if no overlap skip + if len(list(set(migrant_tasks_names) - set(neigh_tasks) )) == len(migrant_tasks_names): + continue + else: + found_mem_block = True + break + + + if found_pe_block and found_mem_block : + return True + else: + return False + + def get_feasible_transformations(self, ex_dp, sim_dp, hot_blck_synced, selected_metric, selected_krnl, sorted_metric_dir): + + # if this knob is set, we randomly pick a transformation + # THis is to illustrate the architectural awareness of FARSI a + if config.transformation_selection_mode == "random": + all_transformations = config.all_available_transformations + return all_transformations + + # pick a transformation smartly + imm_block = self.dh.get_immediate_block_multi_metric(hot_blck_synced, selected_metric, sorted_metric_dir, hot_blck_synced.get_tasks_of_block()) + task = ex_dp.get_hardware_graph().get_task_graph().get_task_by_name(selected_krnl.get_task_name()) + feasible_transformations = set(config.metric_trans_dict[selected_metric]) + + # find the block that is at least as good as the block (for migration) + # if can't find any, we return the same block + selected_metric = list(sorted_metric_dir.keys())[-1] + selected_dir = sorted_metric_dir[selected_metric] + + equal_imm_block_present_for_migration, found_blck_to_mig_to, selection_mode, parallelism_type, locality_type = self.select_block_to_migrate_to(ex_dp, sim_dp, hot_blck_synced, + selected_metric, sorted_metric_dir, selected_krnl) + + hot_block_type = hot_blck_synced.type + hot_block_subtype = hot_blck_synced.subtype + + parallelism_exist, parallelism_type = self.check_if_task_can_run_with_any_other_task_in_parallel(sim_dp, task, hot_blck_synced) + other_block_parallelism_exist = False + all_transformations = config.metric_trans_dict[selected_metric] + can_improve_locality = self.can_improve_locality(ex_dp, hot_blck_synced, task) + can_improve_routing = self.can_improve_routing(ex_dp, sim_dp, hot_blck_synced, task) + + bus_has_pe_mem_topology_for_split = self.bus_has_pe_mem_topology_for_split(ex_dp, sim_dp, task,hot_blck_synced) + # ------------------------ + # based on parallelism, generate feasible transformations + # ------------------------ + if parallelism_exist: + if selected_metric == "latency": + if selected_dir == -1: + if hot_block_type == "pe": + feasible_transformations = ["migrate", "split"] # only for PE since we wont to be low cost, for IC/MEM cost does not increase if you customize + else: + if hot_block_type == "ic": + mem_neighs = [el for el in hot_blck_synced.get_neighs() if el.type == "mem"] + pe_neighs = [el for el in hot_blck_synced.get_neighs() if el.type == "pe"] + if len(mem_neighs) <= 1 or len(pe_neighs) <= 1 or not bus_has_pe_mem_topology_for_split: + feasible_transformations = ["swap", "split_swap"] # ", "swap", "split_swap"] + else: + feasible_transformations = ["migrate", "split"] # ", "swap", "split_swap"] + else: + feasible_transformations = ["migrate", "split"] #", "swap", "split_swap"] + else: + # we can do better by comparing the advantage disadvantage of migrating + # (Advantage: slowing down by serialization, and disadvantage: accelerating by parallelization) + feasible_transformations = ["swap"] + if selected_metric == "power": + if selected_dir == -1: + # we can do better by comparing the advantage disadvantage of migrating + # (Advantage: slowing down by serialization, and disadvantage: accelerating by parallelization) + feasible_transformations = ["swap", "split_swap"] + else: + feasible_transformations = all_transformations + if selected_metric == "area": + if selected_dir == -1: + if hot_block_subtype == "pe": + feasible_transformations = ["migrate", "swap"] + else: + feasible_transformations = ["migrate", "swap", "split_swap"] + else: + feasible_transformations = all_transformations + elif not parallelism_exist: + if selected_metric == "latency": + if selected_dir == -1: + feasible_transformations = ["swap", "split_swap"] + else: + feasible_transformations = ["swap", "migrate"] + if selected_metric == "power": + if selected_dir == -1: + feasible_transformations = ["migrate", "swap", "split_swap"] + if selected_metric == "area": + if selected_dir == -1: + feasible_transformations = ["migrate", "swap","split_swap"] + else: + feasible_transformations = ["migrate", "swap", "split"] + + # ------------------------ + # based on locality, generate feasible transformations + # ------------------------ + if can_improve_locality and ('transfer' in config.all_available_transformations): + # locality not gonna improve area with the current set up + if not selected_metric == "area" and selected_dir == -1: + feasible_transformations.append("transfer") + + #------------------------ + # there is a on opportunity for routing + #------------------------ + if can_improve_routing and ('routing' in config.all_available_transformations): + transformation_list = list(feasible_transformations) + transformation_list.append('routing') + feasible_transformations = set(transformation_list) + + + #------------------------ + # post processing of the destination blocks to eliminate transformations + #------------------------ + # filter migrate + if not found_blck_to_mig_to: + # if can't find a block that is at least as good as the current block, can't migrate + feasible_transformations = set(list(set(feasible_transformations) - set(['migrate']))) + + # filter split + number_of_task_on_block = 0 + if hot_blck_synced.type == "pe": + number_of_task_on_block = len(hot_blck_synced.get_tasks_of_block()) + else: + number_of_task_on_block = len(hot_blck_synced.get_tasks_of_block_by_dir("write")) + if number_of_task_on_block == 1: # can't split an accelerator + feasible_transformations = set(list(set(feasible_transformations) - set(['split', 'split_swap'] ))) + + # filter swap + block_metric_attr = self.get_block_attr(selected_metric) # metric to pay attention to + if getattr(imm_block, block_metric_attr) == getattr(hot_blck_synced, block_metric_attr): + #if imm_block.get_generic_instance_name() == hot_blck_synced.get_generic_instance_name(): + # if can't swap improve, get rid of swap + feasible_transformations = set(list(set(feasible_transformations) - set(['swap']))) + + # for IC's we don't use migrate + if hot_blck_synced.type in ["ic"]: + # we don't cover migrate for ICs at the moment + # TODO: add this feature later + feasible_transformations = set(list(set(feasible_transformations) - set(['migrate', 'split_swap']))) + + # if no valid transformation left, issue the identity transformation (where nothing changes and a simple copying is done) + if len(list(set(feasible_transformations))) == 0: + feasible_transformations = ["identity"] + + + return feasible_transformations + + def set_design_space_size(self, ex_dp, sim_dp): + # if this knob is set, we randomly pick a transformation + # THis is to illustrate the architectural awareness of FARSI a + + buses = [el for el in ex_dp.get_blocks() if el.type == "ic"] + mems = [el for el in ex_dp.get_blocks() if el.type == "mem"] + srams = [el for el in ex_dp.get_blocks() if el.type == "sram"] + drams = [el for el in ex_dp.get_blocks() if el.type == "dram"] + pes = [el for el in ex_dp.get_blocks() if el.type == "pe"] + ips = [el for el in ex_dp.get_blocks() if el.subtype == "ip"] + gpps = [el for el in ex_dp.get_blocks() if el.subtype == "gpp"] + all_blocks = ex_dp.get_blocks() + + # per block + # for PEs + for pe in gpps: + number_of_task_on_block = len(pe.get_tasks_of_block()) + #sim_dp.neighbouring_design_space_size["hardening"] += number_of_task_on_block + 1# +1 for swap, the rest is for split_swap + sim_dp.neighbouring_design_space_size += number_of_task_on_block + 1 # +1 for swap, the rest is for split_swap + for pe in ips: + #sim_dp.neighbouring_design_space_size["softening"] += 1 + sim_dp.neighbouring_design_space_size += 1 + + # for all + for blck in all_blocks: + for mode in ["frequency_modulation", "bus_width_modulation", "loop_iteration_modulation"]: + if not blck.type =="pe": + if mode == "loop_iteration_modulation": + continue + #sim_dp.neighbouring_design_space_size[mode] += 2 # going up or down + sim_dp.neighbouring_design_space_size += 2 # going up or down + + for blck in all_blocks: + for mode in ["allocation"]: + if blck.type == "ic": + continue + number_of_task_on_block = len(blck.get_tasks_of_block()) + #sim_dp.neighbouring_design_space_size[mode] += number_of_task_on_block + 1 # +1 is for split, the rest os for split_swap + sim_dp.neighbouring_design_space_size += number_of_task_on_block + 1 # +1 is for split, the rest os for split_swap + + for blck in all_blocks: + equal_imm_blocks_present_for_migration = self.dh.get_equal_immediate_blocks_present(ex_dp, + blck, + "latency", + -1, + blck.get_tasks_of_block()) + + equal_imm_blocks_present_for_migration.extend(self.dh.get_equal_immediate_blocks_present(ex_dp, + blck, + "latency", + +1, + blck.get_tasks_of_block())) + + """ + imm_blocks_present_for_migration.extend([self.dh.get_immediate_block( + blck, + "latency", + -1, + blck.get_tasks_of_block())]) + """ + #other_blocks_to_map_to_lengths = len(equal_imm_blocks_present_for_migration) - len(imm_blocks_present_for_migration) # subtract to avoid double counting + other_blocks_to_map_to_lengths = 0 + for el in equal_imm_blocks_present_for_migration: + if el == blck: + continue + elif not el.type == blck.type: + continue + else: + other_blocks_to_map_to_lengths +=1 + + #other_blocks_to_map_to_lengths = len(equal_imm_blocks_present_for_migration) + #sim_dp.neighbouring_design_space_size[blck.type+"_"+"mapping"] += len(blck.get_tasks_of_block())*other_blocks_to_map_to_lengths + sim_dp.neighbouring_design_space_size += len(blck.get_tasks_of_block())*other_blocks_to_map_to_lengths + + + def get_transformation_design_space_size(self, move_to_apply, ex_dp, sim_dp, block_of_interest, selected_metric, sorted_metric_dir): + # if this knob is set, we randomly pick a transformation + # THis is to illustrate the architectural awareness of FARSI a + imm_block = self.dh.get_immediate_block_multi_metric(block_of_interest, selected_metric, sorted_metric_dir, block_of_interest.get_tasks_of_block()) + + task = (block_of_interest.get_tasks_of_block())[0] # any task for do + feasible_transformations = set(config.metric_trans_dict[selected_metric]) + + # find the block that is at least as good as the block (for migration) + # if can't find any, we return the same block + selected_metric = list(sorted_metric_dir.keys())[-1] + selected_dir = sorted_metric_dir[selected_metric] + + equal_imm_blocks_present_for_migration = self.dh.get_equal_immediate_blocks_present(ex_dp, block_of_interest, + selected_metric, selected_dir, [task]) + if len(equal_imm_blocks_present_for_migration) == 1 and equal_imm_blocks_present_for_migration[0] == block_of_interest: + equal_imm_blocks_present_for_migration = [] + + buses = [el for el in ex_dp.get_blocks() if el.type == "ic"] + mems = [el for el in ex_dp.get_blocks() if el.type == "mem"] + srams = [el for el in ex_dp.get_blocks() if el.type == "sram"] + drams = [el for el in ex_dp.get_blocks() if el.type == "dram"] + pes = [el for el in ex_dp.get_blocks() if el.type == "pe"] + ips = [el for el in ex_dp.get_blocks() if el.subtype == "ip"] + gpps = [el for el in ex_dp.get_blocks() if el.subtype == "gpp"] + + # per block + # for PEs + if block_of_interest.subtype == "gpp": + number_of_task_on_block = len(block_of_interest.get_tasks_of_block()) + move_to_apply.design_space_size["hardening"] += number_of_task_on_block + 1# +1 for swap, the rest is for split_swap + move_to_apply.design_space_size["pe_allocation"] += (number_of_task_on_block + 1) # +1 is for split, the rest os for split_swap + elif block_of_interest.subtype == "ip": + move_to_apply.design_space_size["softening"] += 1 + + # for all + for mode in ["frequency_modulation", "bus_width_modulation", "loop_iteration_modulation", "allocation"]: + if not block_of_interest.type =="pe": + if mode == "loop_iteration_modulation": + continue + value = self.dh.get_all_compatible_blocks_of_certain_char(ex_dp, block_of_interest, + selected_metric, selected_dir, [task], mode) + if mode in ["bus_width_modulation","loop_iteration_modulation"]: + move_to_apply.design_space_size[mode] += len(value) + else: + move_to_apply.design_space_size[block_of_interest.type + "_"+ mode] += len(value) + + + for block_type in ["pe", "mem", "ic"]: + if block_type == block_of_interest.type: + move_to_apply.design_space_size[block_type +"_"+"mapping"] += (len(equal_imm_blocks_present_for_migration) - 1) + else: + move_to_apply.design_space_size[block_type +"_"+"mapping"] += 0 + + can_improve_routing = self.can_improve_routing(ex_dp, sim_dp, block_of_interest, task) + if can_improve_routing: + move_to_apply.design_space_size["routing"] += (len(buses) - 1) + move_to_apply.design_space_size["transfer"] += (len(buses)-1) + move_to_apply.design_space_size["identity"] += 1 + + # pick which transformation to apply + # Variables: + # hot_blck_synced: the block bottleneck + # selected_metric: metric to focus on + # selected_krnl: the kernel to focus on + # ------------------------------ + def select_transformation(self, ex_dp, sim_dp, hot_blck_synced, selected_metric, selected_krnl, sorted_metric_dir): + feasible_transformations = self.get_feasible_transformations(ex_dp, sim_dp, hot_blck_synced, selected_metric, + selected_krnl, sorted_metric_dir) + if config.print_info_regularly: + print(list(feasible_transformations)) + random.seed(datetime.now().microsecond) + # pick randomly at the moment. + # TODO: possibly can do better + transformation = random.choice(list(feasible_transformations)) + + #if len(hot_blck_synced.get_tasks_of_block_by_dir("write")) > 1: + # transformation = "split_swap" + #else: + # transformation = "swap" + if transformation == "migrate": + batch_mode = "single" + transformation_sub_name = "irrelevant" + elif transformation == "split": + # see if any task can run in parallel + batch_mode = "batch" + transformation_sub_name = "irrelevant" + elif transformation == "split_swap": + batch_mode = "single" + transformation_sub_name = "irrelevant" + elif transformation == "transfer": + batch_mode = "irrelevant" + transformation_sub_name = "locality_improvement" + elif transformation == "routing": + batch_mode = "irrelevant" + transformation_sub_name = "routing_improvement" + else: + transformation_sub_name = "irrelevant" + batch_mode = "irrelevant" + + + return transformation, transformation_sub_name, batch_mode, len(list(feasible_transformations)) + + # calculate the cost impact of a kernel improvement + def get_swap_improvement_cost(self, sim_dp, kernels, selected_metric, dir): + def get_subtype_for_cost(block): + if block.type == "pe" and block.subtype == "ip": + return "ip" + if block.type == "pe" and block.subtype == "gpp": + if "A53" in block.instance_name or "ARM" in block.instance_name: + return "arm" + if "G3" in block.instance_name: + return "dsp" + else: + return block.type + + # Figure out whether there is a mapping that improves kernels performance + def no_swap_improvement_possible(sim_dp, selected_metric, metric_dir, krnl): + hot_block = sim_dp.get_dp_stats().get_hot_block_of_krnel(krnl.get_task_name(), selected_metric) + imm_block = self.dh.get_immediate_block_multi_metric(hot_block, metric_dir, [krnl.get_task()]) + blah = hot_block.get_generic_instance_name() + blah2 = imm_block.get_generic_instance_name() + return hot_block.get_generic_instance_name() == imm_block.get_generic_instance_name() + + + # find the cost of improvement by comparing the current and accelerated design (for the kernel) + kernel_improvement_cost = {} + kernel_name_improvement_cost = {} + for krnel in kernels: + hot_block = sim_dp.get_dp_stats().get_hot_block_of_krnel(krnel.get_task_name(), selected_metric) + hot_block_subtype = get_subtype_for_cost(hot_block) + current_cost = self.database.db_input.porting_effort[hot_block_subtype] + #if hot_block_subtype == "ip": + # print("what") + imm_block = self.dh.get_immediate_block_multi_metric(hot_block,selected_metric, metric_dir,[krnel.get_task()]) + imm_block_subtype = get_subtype_for_cost(imm_block) + imm_block_cost = self.database.db_input.porting_effort[imm_block_subtype] + improvement_cost = (imm_block_cost - current_cost) + kernel_improvement_cost[krnel] = improvement_cost + + # calcualte inverse so lower means worse + max_val = max(kernel_improvement_cost.values()) # multiply by + kernel_improvement_cost_inverse = {} + for k, v in kernel_improvement_cost.items(): + kernel_improvement_cost_inverse[k] = max_val - kernel_improvement_cost[k] + + # get sum and normalize + sum_ = sum(list(kernel_improvement_cost_inverse.values())) + for k, v in kernel_improvement_cost_inverse.items(): + # normalize + if not (sum_ == 0): + kernel_improvement_cost_inverse[k] = kernel_improvement_cost_inverse[k]/sum_ + kernel_improvement_cost_inverse[k] = max(kernel_improvement_cost_inverse[k], .0000001) + if no_swap_improvement_possible(sim_dp, selected_metric, dir, k): + kernel_improvement_cost_inverse[k] = .0000001 + kernel_name_improvement_cost[k.get_task_name()] = kernel_improvement_cost_inverse[k] + + return kernel_improvement_cost_inverse + + def get_identity_cost(self): + return self.database.db_input.porting_effort["ip"] + + + # calculate the cost impact of a kernel improvement + def get_swap_cost(self, sim_dp, krnl, selected_metric, sorted_metric_dir): + def get_subtype_for_cost(block): + if block.type == "pe" and block.subtype == "ip": + return "ip" + if block.type == "pe" and block.subtype == "gpp": + if "A53" in block.instance_name or "ARM" in block.instance_name: + return "arm" + if "G3" in block.instance_name: + return "dsp" + else: + return block.type + + hot_block = sim_dp.get_dp_stats().get_hot_block_of_krnel(krnl.get_task_name(), selected_metric) + hot_block_subtype = get_subtype_for_cost(hot_block) + current_cost = self.database.db_input.porting_effort[hot_block_subtype] + imm_block = self.dh.get_immediate_block_multi_metric(hot_block,selected_metric, sorted_metric_dir,[krnl.get_task()]) + imm_block_subtype = get_subtype_for_cost(imm_block) + imm_block_cost = self.database.db_input.porting_effort[imm_block_subtype] + improvement_cost = (imm_block_cost - current_cost) + return improvement_cost + + def get_migrate_cost(self): + return 0 + + def get_transfer_cost(self): + return 0 + + def get_routing_cost(self): + return 0 + + def get_split_cost(self): + return 1 + + def get_migration_split_cost(self, transformation): + if transformation == "migrate": + return self.get_migrate_cost() + elif transformation == "split": + return self.get_split_cost() + else: + print("this transformation" + transformation + " is not supported for cost calculation") + exit(0) + + # how much does it cost to improve the kernel for different transformations + def get_krnl_improvement_cost(self, ex_dp, sim_dp, krnls, selected_metric, move_sorted_metric_dir): + # whether you can apply the transformation for the krnel's block + def get_transformation_cost(sim_dp, selected_metric, move_sorted_metric_dir, krnl, transformation): + if transformation == "swap": + cost = self.get_swap_cost(sim_dp, krnl, selected_metric, move_sorted_metric_dir) + elif transformation in ["split", "migrate"]: + cost = self.get_migration_split_cost(transformation) + elif transformation in ["split_swap"]: + cost = self.get_migration_split_cost("split") + cost += self.get_swap_cost(sim_dp, krnl, selected_metric, move_sorted_metric_dir) + elif transformation in ["identity"]: + cost = self.get_identity_cost() + elif transformation in ["transfer"]: + cost = self.get_transfer_cost() + elif transformation in ["routing"]: + cost = self.get_routing_cost() + if cost == 0: + cost = self.min_cost_to_consider + return cost + + krnl_improvement_cost = {} + + # iterate through the kernels, find their feasible transformations and + # find cost + for krnl in krnls: + hot_block = sim_dp.get_dp_stats().get_hot_block_of_krnel(krnl.get_task_name(), selected_metric) + imm_block = self.dh.get_immediate_block_multi_metric(hot_block, selected_metric, move_sorted_metric_dir, [krnl.get_task()]) + hot_blck_synced = self.dh.find_cores_hot_kernel_blck_bottlneck(ex_dp, hot_block) + feasible_trans = self.get_feasible_transformations(ex_dp, sim_dp, hot_blck_synced, selected_metric, + krnl,move_sorted_metric_dir) + for trans in feasible_trans: + cost = get_transformation_cost(sim_dp, selected_metric, move_sorted_metric_dir, krnl, trans) + krnl_improvement_cost[(krnl, trans)] = cost + return krnl_improvement_cost + + # select a metric to improve on + def select_metric(self, sim_dp): + # prioritize metrics based on their distance contribution to goal + metric_prob_dict = {} # (metric:priority value) each value is in [0 ,1] interval + for metric in config.budgetted_metrics: + metric_prob_dict[metric] = sim_dp.dp_stats.dist_to_goal_per_metric(metric, config.metric_sel_dis_mode)/\ + sim_dp.dp_stats.dist_to_goal(["power", "area", "latency"], + config.metric_sel_dis_mode) + + # sort the metric based on distance (and whether the sort is probabilistic or exact). + # probabilistic sorting, first sort exactly, then use the exact value as a probability of selection + metric_prob_dict_sorted = {k: v for k, v in sorted(metric_prob_dict.items(), key=lambda item: item[1])} + if config.move_metric_ranking_mode== "exact": + selected_metric = list(metric_prob_dict_sorted.keys())[len(metric_prob_dict_sorted.keys()) -1] + else: + selected_metric = self.pick_from_prob_dict(metric_prob_dict_sorted) + + sorted_low_to_high_metric_dir = {} + for metric, prob in metric_prob_dict_sorted.items(): + move_dir = 1 # try to increase the metric value + if not sim_dp.dp_stats.fits_budget_for_metric_for_SOC(metric, 1): + move_dir = -1 # try to reduce the metric value + sorted_low_to_high_metric_dir[metric] = move_dir + + # Delete later. for now for validation + #selected_metric = "latency" + #sorted_low_to_high_metric_dir= {'area':1, 'power':-1, 'latency':-1} + #metric_prob_dict_sorted = {'area':.1, 'power':.1, 'latency':.8} + + return selected_metric, metric_prob_dict_sorted, sorted_low_to_high_metric_dir + + # select direction for the move + def select_dir(self, sim_dp, metric): + move_dir = 1 # try to increase the metric value + if not sim_dp.dp_stats.fits_budget_for_metric_for_SOC(metric, 1): + move_dir = -1 # try to reduce the metric value + return move_dir + + def filter_in_kernels_meeting_budget(self, selected_metric, sim_dp): + krnls = sim_dp.get_dp_stats().get_kernels() + + # filter the kernels whose workload already met the budget + workload_tasks = sim_dp.database.db_input.workload_tasks + task_workload = sim_dp.database.db_input.task_workload + workloads_to_consider = [] + for workload in workload_tasks.keys(): + if sim_dp.dp_stats.workload_fits_budget(workload, 1): + continue + workloads_to_consider.append(workload) + + krnls_to_consider = [] + for krnl in krnls: + if task_workload[krnl.get_task_name()] in workloads_to_consider and not krnl.get_task().is_task_dummy(): + krnls_to_consider.append(krnl) + + return krnls_to_consider + + # get each kernels_contribution to the metric of interest + def get_kernels_s_contribution(self, selected_metric, sim_dp): + krnl_prob_dict = {} # (kernel, metric_value) + + + #krnls = sim_dp.get_dp_stats().get_kernels() + # filter it kernels whose workload meet the budget + krnls = self.filter_in_kernels_meeting_budget(selected_metric, sim_dp) + if krnls == []: # the design meets the budget, hence all kernels can be improved for cost improvement + krnls = sim_dp.get_dp_stats().get_kernels() + + metric_total = sum([krnl.stats.get_metric(selected_metric) for krnl in krnls]) + # sort kernels based on their contribution to the metric of interest + for krnl in krnls: + krnl_prob_dict[krnl] = krnl.stats.get_metric(selected_metric)/metric_total + + if not "bottleneck" in self.move_s_krnel_selection: + for krnl in krnls: + krnl_prob_dict[krnl] = 1 + return krnl_prob_dict + + # get each_kernels_improvement_ease (ease = 1/cost) + def get_kernels_s_improvement_ease(self, ex_dp, sim_dp, selected_metric, move_sorted_metric_dir): + krnls = sim_dp.get_dp_stats().get_kernels() + krnl_improvement_ease = {} + if not "improvement_ease" in self.move_s_krnel_selection: + for krnl in krnls: + krnl_improvement_ease[krnl] = 1 + else: + krnl_trans_improvement_cost = self.get_krnl_improvement_cost(ex_dp, sim_dp, krnls, selected_metric, move_sorted_metric_dir) + # normalize + # normalized and reverse (we need to reverse, so higher cost is worse, i.e., smaller) + krnl_trans_improvement_ease = {} + for krnl_trans, cost in krnl_trans_improvement_cost.items(): + krnl_trans_improvement_ease[krnl_trans] = 1 / (cost) + max_ease = max(krnl_trans_improvement_ease.values()) + for krnl_trans, ease in krnl_trans_improvement_ease.items(): + krnl_trans_improvement_ease[krnl_trans] = ease / max_ease + + for krnl in krnls: + krnl_improvement_ease[krnl] = 0 + + for krnl_trans, ease in krnl_trans_improvement_ease.items(): + krnl, trans = krnl_trans + krnl_improvement_ease[krnl] = max(ease, krnl_improvement_ease[krnl]) + + + return krnl_improvement_ease + + # select the kernel for the move + def select_kernel(self, ex_dp, sim_dp, selected_metric, move_sorted_metric_dir): + + # get each kernel's contributions + krnl_contribution_dict = self.get_kernels_s_contribution(selected_metric, sim_dp) + # get each kernel's improvement cost + krnl_improvement_ease = self.get_kernels_s_improvement_ease(ex_dp, sim_dp, selected_metric, move_sorted_metric_dir) + + + + + # combine the selections methods + # multiply the probabilities for a more complex metric + krnl_prob_dict = {} + for krnl in krnl_contribution_dict.keys(): + krnl_prob_dict[krnl] = krnl_contribution_dict[krnl] * krnl_improvement_ease[krnl] + + # give zero probablity to the krnls that you filtered out + for krnl in sim_dp.get_dp_stats().get_kernels(): + if krnl not in krnl_prob_dict.keys(): + krnl_prob_dict[krnl] = 0 + # sort + #krnl_prob_dict_sorted = {k: v for k, v in sorted(krnl_prob_dict.items(), key=lambda item: item[1])} + krnl_prob_dict_sorted = sorted(krnl_prob_dict.items(), key=lambda item: item[1], reverse=True) + + # get the worse kernel + if config.move_krnel_ranking_mode == "exact": # for area to allow us pick scenarios that are not necessarily the worst + #selected_krnl = list(krnl_prob_dict_sorted.keys())[ + # len(krnl_prob_dict_sorted.keys()) - 1 - self.krnel_rnk_to_consider] + for krnl, prob in krnl_prob_dict_sorted: + if krnl.get_task_name() in self.krnels_not_to_consider: + continue + selected_krnl = krnl + break + else: + selected_krnl = self.pick_from_prob_dict(krnl_prob_dict_sorted) + + if config.transformation_selection_mode == "random": + krnls = sim_dp.get_dp_stats().get_kernels() + random.seed(datetime.now().microsecond) + selected_krnl = random.choice(krnls) + + return selected_krnl, krnl_prob_dict, krnl_prob_dict_sorted + + # select blocks for the move + def select_block(self, sim_dp, ex_dp, selected_krnl, selected_metric): + # get the hot block for the kernel. Hot means the most contributing block for the kernel/metric of interest + hot_blck = sim_dp.get_dp_stats().get_hot_block_of_krnel(selected_krnl.get_task_name(), selected_metric) + + # randomly pick one + if config.transformation_selection_mode =="random": + random.seed(datetime.now().microsecond) + hot_blck = any_block = random.choice(ex_dp.get_hardware_graph().get_blocks()) # this is just dummmy to prevent breaking the plotting + + # hot_blck_synced is the same block but ensured that the block instance + # is chosen from ex instead of sim, so it can be modified + hot_blck_synced = self.dh.find_cores_hot_kernel_blck_bottlneck(ex_dp, hot_blck) + block_prob_dict = sim_dp.get_dp_stats().get_hot_block_of_krnel_sorted(selected_krnl.get_task_name(), selected_metric) + return hot_blck_synced, block_prob_dict + + def select_block_without_sync(self, sim_dp, selected_krnl, selected_metric): + # get the hot block for the kernel. Hot means the most contributing block for the kernel/metric of interest + hot_blck = sim_dp.get_dp_stats().get_hot_block_of_krnel(selected_krnl.get_task_name(), selected_metric) + # hot_blck_synced is the same block but ensured that the block instance + # is chosen from ex instead of sim, so it can be modified + block_prob_dict = sim_dp.get_dp_stats().get_hot_block_of_krnel_sorted(selected_krnl.get_task_name(), selected_metric) + return hot_blck, block_prob_dict + + def change_read_task_to_write_if_necessary(self, ex_dp, sim_dp, move_to_apply, selected_krnl): + tasks_synced = [task__ for task__ in move_to_apply.get_block_ref().get_tasks_of_block() if + task__.name == selected_krnl.get_task_name()] + if len(tasks_synced) == 0: # this condition happens when we have a read task and we have unloaded reads + krnl_s_tsk = ex_dp.get_hardware_graph().get_task_graph().get_task_by_name( + move_to_apply.get_kernel_ref().get_task_name()) + parents_s_task = [el.get_name() for el in + ex_dp.get_hardware_graph().get_task_graph().get_task_s_parents(krnl_s_tsk)] + tasks_on_block = [el.get_name() for el in move_to_apply.get_block_ref().get_tasks_of_block()] + for parent_task in parents_s_task: + if parent_task in tasks_on_block: + parents_task_obj = ex_dp.get_hardware_graph().get_task_graph().get_task_by_name(parent_task) + krnl = sim_dp.get_kernel_by_task_name(parents_task_obj) + move_to_apply.set_krnel_ref(krnl) + return + + def find_block_with_sharing_tasks(self, ex_dp, selected_block, selected_krnl): + succeeded = False + # not covering ic at the moment + if selected_block.type =="ic": + return succeeded, "_" + + # get task of block + cur_block_tasks = [el.get_name() for el in selected_block.get_tasks_of_block()] + krnl_task = selected_krnl.get_task().get_name() + # get other blocks in the system + all_blocks = ex_dp.get_hardware_graph().get_blocks() + all_blocks_minus_src_block = list(set(all_blocks) - set([selected_block])) + assert(len(all_blocks) == len(all_blocks_minus_src_block) +1), "all_blocks must have one more block in it" + + if selected_block.type == "pe": + blocks_with_sharing_task_type = "mem" + elif selected_block.type == "mem": + blocks_with_sharing_task_type = "pe" + + blocks_to_look_at = [blck for blck in all_blocks_minus_src_block if blck.type == blocks_with_sharing_task_type] + + # iterate through ic neighs (of oppotie type, i.e., for mem, look for pe, and for pe look for mem), + # and look for shared tasks. If there is no shared task, we should move the block somewhere where there is + # sort the neighbours based on the number of sharings. + blocks_sorted_based_on_sharing = sorted(blocks_to_look_at, key=lambda blck: len(list(set(cur_block_tasks) - set([el.get_name() for el in blck.get_tasks_of_block()])))) + for block_with_sharing in blocks_sorted_based_on_sharing: + block_tasks = [el.get_name() for el in block_with_sharing.get_tasks_of_block()] + if krnl_task in block_tasks: + return True, block_with_sharing + else: + return False, "_" + + + + def find_improve_routing(self, ex_dp, sim_dp, selected_block, selected_krnl_task): + if not selected_block.type == "ic": + return None, None + result = True + task_name = selected_krnl_task.get_task_name() + task_s_blocks = ex_dp.get_hardware_graph().get_blocks_of_task_by_name(task_name) + pe = [blk for blk in task_s_blocks if blk.type == "pe"][0] + mems =[blk for blk in task_s_blocks if blk.type == "mem"] + + ic_entry, ic_exit = None, None + for mem in mems: + path= sim_dp.dp.get_hardware_graph().get_path_between_two_vertecies(pe, mem) + if len(path)> 4: # more than two ICs + ic_entry = path[1] + ic_exit = path[-2] + break + + success = not(ic_entry==None) + return success, ic_exit + + def can_improve_routing(self, ex_dp, sim_dp, selected_block, selected_krnl_task): + if not selected_block.type == "ic": + return False + result = True + task_name = selected_krnl_task.get_name() + task_s_blocks = ex_dp.get_hardware_graph().get_blocks_of_task_by_name(task_name) + pe =[blk for blk in task_s_blocks if blk.type == "pe"][0] + mems =[blk for blk in task_s_blocks if blk.type == "mem"] + + for mem in mems: + path= ex_dp.get_hardware_graph().get_path_between_two_vertecies(pe, mem) + if len(path) > 4: # more than two ICs + return True + return False + + def can_improve_locality(self, ex_dp, selected_block, selected_krnl_task): + result = True + + # not covering ic at the moment + if selected_block.type =="ic": + return False + + # get task of block + cur_block_tasks = list(set([el.get_name() for el in selected_block.get_tasks_of_block()])) + # get neighbouring ic + ic = [neigh for neigh in selected_block.get_neighs() if neigh.type == "ic"][0] + if selected_block.type == "pe": + blocks_with_sharing_task_type = "mem" + elif selected_block.type == "mem": + blocks_with_sharing_task_type = "pe" + + ic_neighs = [neigh for neigh in ic.get_neighs() if neigh.type == blocks_with_sharing_task_type] + + # iterate through ic neighs (of oppotie type, i.e., for mem, look for pe, and for pe look for mem), + # and look for shared tasks. If there is no shared task, we should move the block somewhere where there is + for block in ic_neighs: + block_tasks = list(set([el.get_name() for el in block.get_tasks_of_block()])) + shared_tasks_exist = len(list(set(cur_block_tasks) - set(block_tasks))) < len(list(set(cur_block_tasks))) + if shared_tasks_exist: + result = False + break + return result + + def find_absorbing_block_tuple(self, ex_dp, sim_dp, task_name, block): + task_pe = None + for blk in ex_dp.get_hardware_graph().get_blocks(): + # only cover absorbing for PEs + if not blk.type == "pe" : + continue + blk_tasks = [el.get_name() for el in el.get_tasks_of_block()] + if task_name in blk_tasks: + task_pe = blk + break + + ic_neigh = [neigh for neigh in task_pe.get_neighs() if neigh.type == "ic"][0] + ic_neigh_neigh = [neigh for neigh in ic_neigh.get_neighs() if neigh.type == "ic"][0] + + # we don't mess with system ic + if self.is_system_ic(ex_dp, sim_dp, ic_neigh): + absorbee, absorber = None, None + else: + # if the ic didn't have any memory attached to it, return true + mem_neighs = [neigh for neigh in ic_neigh.get_neighs() if neigh.type == "mem"] + if len(mem_neighs) == 0: + absorbee, absorber = ic_neigh, ic_neigh_neigh + else: + absorbee, absorber = None, None + + return absorbee, absorber + + def can_absorb_block(self, ex_dp, sim_dp, task_name): + task_pe = None + for blk in ex_dp.get_hardware_graph().get_blocks(): + # only cover absorbing for PEs + if not blk.type == "pe" : + continue + blk_tasks = [el.get_name() for el in blk.get_tasks_of_block()] + if task_name in blk_tasks: + task_pe = blk + break + + ic_neigh = [neigh for neigh in task_pe.get_neighs() if neigh.type == "ic"][0] + # we don't mess with system ic + if self.is_system_ic(ex_dp, sim_dp, ic_neigh): + result = False + else: + # if the ic didn't have any memory attached to it, return true + mem_neighs = [neigh for neigh in ic_neigh.get_neighs() if neigh.type == "mem"] + if len(mem_neighs) == 0: + result = True + else: + result = False + + return result + + # ------------------------------ + # Functionality: + # generate a move to apply. A move consists of a metric, direction, kernel, block and transformation. + # At the moment, we target the metric that is most further from the budget. Kernel and block are chosen + # based on how much they contribute to the distance. + # + # Variables: + # des_tup: (design, simulated design) + # ------------------------------ + def sel_moves_based_on_dis(self, des_tup): + ex_dp, sim_dp = des_tup + if config.DEBUG_FIX: random.seed(0) + else: time.sleep(.00001), random.seed(datetime.now().microsecond) + + # select move components + t_0 = time.time() + selected_metric, metric_prob_dir_dict, sorted_metric_dir = self.select_metric(sim_dp) + t_1 = time.time() + move_dir = self.select_dir(sim_dp, selected_metric) + t_2 = time.time() + selected_krnl, krnl_prob_dict, krnl_prob_dir_dict_sorted = self.select_kernel(ex_dp, sim_dp, selected_metric, sorted_metric_dir) + t_3 = time.time() + selected_block, block_prob_dict = self.select_block(sim_dp, ex_dp, selected_krnl, selected_metric) + t_4 = time.time() + transformation_name,transformation_sub_name, transformation_batch_mode, total_transformation_cnt = self.select_transformation(ex_dp, sim_dp, selected_block, selected_metric, selected_krnl, sorted_metric_dir) + t_5 = time.time() + + self.set_design_space_size(des_tup[0], des_tup[1]) + + + """ + if sim_dp.dp_stats.fits_budget(1) and self.dram_feasibility_check_pass(ex_dp) and self.can_improve_locality(selected_block, selected_krnl): + transformation_sub_name = "transfer_no_prune" + transformation_name = "improve_locality" + transformation_batch_mode = "single" + selected_metric = "cost" + """ + # prepare for move + # if bus, (forgot which exception), if IP, avoid split . + """ + if sim_dp.dp_stats.fits_budget(1) and not self.dram_feasibility_check_pass(ex_dp): + transformation_name = "dram_fix" + transformation_sub_name = "dram_fix_no_prune" + transformation_batch_mode = "single" + selected_metric = "cost" + """ + if self.is_cleanup_iter(): + transformation_name = "cleanup" + transformation_sub_name = "non" + transformation_batch_mode = "single" + selected_metric = "cost" + #config.VIS_GR_PER_GEN = True + self.cleanup_ctr += 1 + #config.VIS_GR_PER_GEN = False + + # log the data for future profiling/data collection/debugging + move_to_apply = move(transformation_name, transformation_sub_name, transformation_batch_mode, move_dir, selected_metric, selected_block, selected_krnl, krnl_prob_dir_dict_sorted) + move_to_apply.set_sorted_metric_dir(sorted_metric_dir) + move_to_apply.set_logs(sim_dp.database.db_input.task_workload[selected_krnl.get_task_name()],"workload") + move_to_apply.set_logs(sim_dp.dp_stats.get_system_complex_metric("cost"), "cost") + move_to_apply.set_logs(krnl_prob_dict, "kernels") + move_to_apply.set_logs(metric_prob_dir_dict, "metrics") + move_to_apply.set_logs(block_prob_dict, "blocks") + move_to_apply.set_logs(self.krnel_rnk_to_consider, "kernel_rnk_to_consider") + move_to_apply.set_logs(sim_dp.dp_stats.dist_to_goal(["power", "area", "latency", "cost"], + config.metric_sel_dis_mode),"ref_des_dist_to_goal_all") + move_to_apply.set_logs(sim_dp.dp_stats.dist_to_goal(["power", "area", "latency"], + config.metric_sel_dis_mode),"ref_des_dist_to_goal_non_cost") + + for blck_of_interest in ex_dp.get_blocks(): + self.get_transformation_design_space_size(move_to_apply, ex_dp, sim_dp, blck_of_interest, selected_metric, sorted_metric_dir) + + + move_to_apply.set_logs(t_1 - t_0, "metric_selection_time") + move_to_apply.set_logs(t_2 - t_1, "dir_selection_time") + move_to_apply.set_logs(t_3 - t_2, "kernel_selection_time") + move_to_apply.set_logs(t_4 - t_3, "block_selection_time") + move_to_apply.set_logs(t_5 - t_4, "transformation_selection_time") + + blck_ref = move_to_apply.get_block_ref() + gc.disable() + blck_ref_cp = cPickle.loads(cPickle.dumps(blck_ref, -1)) + gc.enable() + # ------------------------ + # prepare for the move + # ------------------------ + if move_to_apply.get_transformation_name() == "identity": + pass + #move_to_apply.set_validity(False, "NoValidTransformationException") + if move_to_apply.get_transformation_name() == "swap": + self.dh.unload_read_mem(ex_dp) # unload memories + if not blck_ref.type == "ic": + self.dh.unload_buses(ex_dp) # unload buses + else: + self.dh.unload_read_buses(ex_dp) # unload buses + # get immediate superior/inferior block (based on the desired direction) + imm_block = self.dh.get_immediate_block_multi_metric(blck_ref, + move_to_apply.get_metric(), move_to_apply.get_sorted_metric_dir(), + blck_ref.get_tasks_of_block()) # immediate block either superior or + move_to_apply.set_dest_block(imm_block) + move_to_apply.set_customization_type(blck_ref, imm_block) + + move_to_apply.set_tasks(blck_ref.get_tasks_of_block()) + elif move_to_apply.get_transformation_name() in ["split_swap"]: + self.dh.unload_buses(ex_dp) # unload buses + # get immediate superior/inferior block (based on the desired direction) + succeeded,migrant = blck_ref.get_tasks_by_name(move_to_apply.get_kernel_ref().get_task_name()) + if not succeeded: + move_to_apply.set_validity(False, "NoMigrantException") + else: + imm_block = self.dh.get_immediate_block_multi_metric(blck_ref, + move_to_apply.get_metric(), move_to_apply.get_sorted_metric_dir(), + [migrant]) # immediate block either superior or + move_to_apply.set_dest_block(imm_block) + + self.dh.unload_read_mem(ex_dp) # unload memories + self.change_read_task_to_write_if_necessary(ex_dp, sim_dp, move_to_apply, selected_krnl) + migrant_tasks = self.dh.migrant_selection(ex_dp, sim_dp, blck_ref, blck_ref_cp, move_to_apply.get_kernel_ref(), + move_to_apply.get_transformation_batch()) + #migrant_tasks = list(set(move_to_apply.get_block_ref().get_tasks()) - set(migrant_tasks_)) # reverse the order to allow for swap to happen on the ref_block + move_to_apply.set_tasks(migrant_tasks) + move_to_apply.set_customization_type(blck_ref, imm_block) + elif move_to_apply.get_transformation_name() in ["split"]: + # select tasks to migrate + #self.change_read_task_to_write_if_necessary(ex_dp, sim_dp, move_to_apply, selected_krnl) + migrant_tasks = self.dh.migrant_selection(ex_dp, sim_dp, blck_ref, blck_ref_cp, move_to_apply.get_kernel_ref(), + move_to_apply.get_transformation_batch()) + + + # determine the parallelism type + parallelism_type_ = [] #with repetition + parallelism_type = [] + migrant_tasks_names = [el.get_name() for el in migrant_tasks] + for task_ in migrant_tasks_names: + parallelism_type_.append(self.get_task_parallelism_type(sim_dp, task_, selected_krnl.get_task_name())) + parallelism_type = list(set(parallelism_type_)) + + move_to_apply.set_parallelism_type(parallelism_type) + move_to_apply.set_tasks(migrant_tasks) + if len(migrant_tasks) == 0: + move_to_apply.set_validity(False, "NoParallelTaskException") + if blck_ref.subtype == "ip": # makes no sense to split the IPs, + # it can actually cause problems where + # we end up duplicating the hardware + move_to_apply.set_validity(False, "IPSplitException") + elif move_to_apply.get_transformation_name() == "migrate": + if not selected_block.type == "ic": # ic migration is not supported + # check and see if tasks exist (if not, it was a read) + imm_block_present, found_blck_to_mig_to, mig_selection_mode, parallelism_type, locality_type = self.select_block_to_migrate_to(ex_dp, + sim_dp, + blck_ref_cp, + move_to_apply.get_metric(), + move_to_apply.get_sorted_metric_dir(), + move_to_apply.get_kernel_ref()) + + + + move_to_apply.set_parallelism_type(parallelism_type) + move_to_apply.set_locality_type(locality_type) + self.dh.unload_buses(ex_dp) # unload buses + self.dh.unload_read_mem(ex_dp) # unload memories + if not imm_block_present.subtype == "ip": + self.change_read_task_to_write_if_necessary(ex_dp, sim_dp, move_to_apply, selected_krnl) + if not found_blck_to_mig_to: + move_to_apply.set_validity(False, "NoMigrantException") + imm_block_present = blck_ref + elif move_to_apply.get_kernel_ref().get_task_name() in ["souurce", "siink", "dummy_last"]: + move_to_apply.set_validity(False, "NoMigrantException") + imm_block_present = blck_ref + else: + migrant_tasks = self.dh.migrant_selection(ex_dp, sim_dp, blck_ref, blck_ref_cp, move_to_apply.get_kernel_ref(), + mig_selection_mode) + + move_to_apply.set_tasks(migrant_tasks) + move_to_apply.set_dest_block(imm_block_present) + else: + move_to_apply.set_validity(False, "ICMigrationException") + elif move_to_apply.get_transformation_name() == "dram_fix": + any_block = ex_dp.get_hardware_graph().get_blocks()[0] # this is just dummmy to prevent breaking the plotting + any_task = any_block.get_tasks_of_block()[0] + move_to_apply.set_tasks([any_task]) # this is just dummmy to prevent breaking the plotting + move_to_apply.set_dest_block(any_block) + pass + elif move_to_apply.get_transformation_name() == "transfer": + if move_to_apply.get_transformation_sub_name() == "locality_improvement": + succeeded, dest_block = self.find_block_with_sharing_tasks(ex_dp, selected_block, selected_krnl) + if succeeded: + move_to_apply.set_dest_block(dest_block) + move_to_apply.set_tasks([move_to_apply.get_kernel_ref().get_task()]) + else: + move_to_apply.set_validity(False, "TransferException") + else: + move_to_apply.set_validity(False, "TransferException") + pass + elif move_to_apply.get_transformation_name() == "routing": + if move_to_apply.get_transformation_sub_name() == "routing_improvement": + succeeded, dest_block = self.find_improve_routing(ex_dp, sim_dp, selected_block, selected_krnl) + if succeeded: + move_to_apply.set_dest_block(dest_block) + move_to_apply.set_tasks([move_to_apply.get_kernel_ref().get_task()]) + else: + move_to_apply.set_validity(False, "RoutingException") + else: + move_to_apply.set_validity(False, "RoutingException") + pass + elif move_to_apply.get_transformation_name() == "cleanup": + if self.can_absorb_block(ex_dp, sim_dp, move_to_apply.get_kernel_ref().get_task_name()): + move_to_apply.set_transformation_sub_name("absorb") + absorbee, absorber = self.find_absorbing_block_tuple(ex_dp, sim_dp, move_to_apply.get_kernel_ref().get_task_name()) + if absorber == None or absorbee == "None": + move_to_apply.set_validity(False, "NoAbsorbee(er)Exception") + else: + move_to_apply.set_ref_block(absorbee) + move_to_apply.set_dest_block(absorber) + else: + move_to_apply.set_validity(False, "CostPairingException") + self.dh.unload_buses(ex_dp) # unload buses + self.dh.unload_read_mem(ex_dp) # unload memories + task_1, block_task_1, task_2, block_task_2 = self.find_task_with_similar_mappable_ips(des_tup) + # we also randomize + if not (task_1 is None) and (random.choice(np.arange(0,1,.1))>.5): + move_to_apply.set_ref_block(block_task_1) + migrant_tasks = [task_1] + imm_block_present = block_task_2 + move_to_apply.set_tasks(migrant_tasks) + move_to_apply.set_dest_block(imm_block_present) + else: + pair = self.gen_block_match_cleanup_move(des_tup) + if len(pair) == 0: + move_to_apply.set_validity(False, "CostPairingException") + else: + ref_block = pair[0] + if not ref_block.type == "ic": # ic migration is not supported + move_to_apply.set_ref_block(ref_block) + migrant_tasks = ref_block.get_tasks_of_block() + imm_block_present = pair[1] + move_to_apply.set_tasks(migrant_tasks) + move_to_apply.set_dest_block(imm_block_present) + + + move_to_apply.set_breadth_depth(self.SA_current_breadth, self.SA_current_depth, self.SA_current_mini_breadth) # set depth and breadth (for debugging/ plotting) + return move_to_apply, total_transformation_cnt + + # ------------------------------ + # Functionality: + # How to choose the move. + # Variables: + # des_tup: (design, simulated design) + # ------------------------------ + def sel_moves(self, des_tup, mode="dist_rank"): # TODO: add mode + if mode == "dist_rank": # rank and choose probabilistically based on distance + return self.sel_moves_based_on_dis(des_tup) + else: + print("mode" + mode + " is not supported") + exit(0) + + # ------------------------------ + # Functionality: + # Calculate possible neighbours, though not randomly. + # des_tup: design tuple. Contains a design tuple (ex_dp, sim_dp). ex_dp: design to find neighbours for. + # sim_dp: simulated ex_dp. + # ------------------------------ + def gen_some_neighs_orchestrated(self, des_tup): + all_possible_moves = config.navigation_moves + ctr = 0 + kernel_pos_to_hndl = self.hot_krnl_pos # for now, but change it + + # generate neighbours until you hit the threshold + while(ctr < self.num_neighs_to_try): + ex_dp, sim_dp = des_tup + # Copy to avoid modifying the current designs. + new_ex_dp_1 = copy.deepcopy(ex_dp) + new_sim_dp_1 = copy.deepcopy(sim_dp) + new_ex_dp = copy.deepcopy(new_ex_dp_1) + new_sim_dp = copy.deepcopy(new_sim_dp_1) + + # apply the move + yield self.dh.apply_move(new_ex_dp, new_sim_dp, all_possible_moves[ctr%len(all_possible_moves)], kernel_pos_to_hndl) + ctr += 1 + return 0 + + def simulated_annealing_energy(self, sim_dp_stats): + return () + + def find_best_design(self,sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_stat_so_far, best_ex_dp_so_far): + if config.heuristic_type == "SA" or config.heuristic_type == "moos": + return self.find_best_design_SA(sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_stat_so_far, best_ex_dp_so_far) + else: + return self.find_best_design_others(sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_stat_so_far, best_ex_dp_so_far) + + + # find the best design from a list + def find_best_design_SA(self, sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_stat_so_far, best_ex_dp_so_far): + # for all metrics, we only return true if there is an improvement, + # it does not make sense to look at block equality (as energy won't be zero in cases that there is a difficult trade off) + if config.sel_next_dp == "all_metrics": + sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics = sorted(sim_dp_stat_ann_delta_energy_dict_all_metrics.items(), + key=lambda x: x[1]) + + if sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0][1] < -.0001: # a very small number + # if a better design (than the best exist), return + return sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0], True + else: + return sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0], False + + + # two blocks are equal if they have the same generic instance name + # and have been scheduled the same tasks + def blocks_are_equal(block_1, block_2): + if not selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + return False + elif selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + # make sure tasks are not the same. + # this is to avoid scenarios where a block is improved (but it's generic name) equal to the + # next block bottleneck. Here we make sure tasks are different + block_1_tasks = [tsk.name for tsk in block_1.get_tasks_of_block()] + block_2_tasks = [tsk.name for tsk in block_1.get_tasks_of_block()] + task_diff = list(set(block_1_tasks) - set(block_2_tasks)) + return len(task_diff) == 0 + + # get the best_sim info + sim_dp = best_sim_dp_stat_so_far.dp + ex_dp = best_ex_dp_so_far + + best_sim_selected_metric, metric_prob_dict, best_sorted_metric_dir = self.select_metric(sim_dp) + best_sim_move_dir = self.select_dir(sim_dp, best_sim_selected_metric) + best_sim_selected_krnl, krnl_prob_dict = self.select_kernel(ex_dp, sim_dp, best_sim_selected_metric, best_sorted_metric_dir) + best_sim_selected_block, block_prob_dict = self.select_block_without_sync(sim_dp, best_sim_selected_krnl, best_sim_selected_metric) + + # sort the design base on distance + sorted_sim_dp_stat_ann_delta_energy_dict = sorted(sim_dp_stat_ann_delta_energy_dict.items(), key=lambda x: x[1]) + + #best_neighbour_stat, best_neighbour_delta_energy = sorted_sim_dp_stat_ann_delta_energy_dict[0] # here we can be smarter + if sorted_sim_dp_stat_ann_delta_energy_dict[0][1] < 0: + # if a better design (than the best exist), return + return sorted_sim_dp_stat_ann_delta_energy_dict[0], True + elif sorted_sim_dp_stat_ann_delta_energy_dict[0][1] == 0: + # if no better design + if len(sorted_sim_dp_stat_ann_delta_energy_dict[0]) == 1: + # if no better design (only one design means that our original design is the one) + return sorted_sim_dp_stat_ann_delta_energy_dict[0], False + else: + # filter out the designs which hasn't seen a distance improvement + sim_dp_to_select_from = [] + for sim_dp_stat, energy in sorted_sim_dp_stat_ann_delta_energy_dict: + #sim_dp_to_select_from.append((sim_dp_stat, energy)) + + designs_to_consider = [] + sim_dp = sim_dp_stat.dp + ex_dp = sim_stat_ex_dp_dict[sim_dp_stat] + selected_metric, metric_prob_dict, sorted_metric_dir = self.select_metric(sim_dp) + move_dir = self.select_dir(sim_dp, selected_metric) + selected_krnl, krnl_prob_dict = self.select_kernel(ex_dp, sim_dp, selected_metric, sorted_metric_dir) + selected_block, block_prob_dict = self.select_block_without_sync(sim_dp, selected_krnl, + selected_metric) + if energy > 0: + designs_to_consider.append((sim_dp_stat, energy)) + return sim_dp_to_select_from[0], False + elif not selected_krnl.get_task_name() == best_sim_selected_krnl.get_task_name(): + designs_to_consider.append((sim_dp_stat, energy)) + return designs_to_consider[0], True + elif not blocks_are_equal(selected_block, best_sim_selected_block): + #elif not selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + designs_to_consider.append((sim_dp_stat, energy)) + return designs_to_consider[0], True + + return sim_dp_to_select_from[0], False + + + # find the best design from a list + def find_best_design_others(self, sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_stat_so_far, best_ex_dp_so_far): + # for all metrics, we only return true if there is an improvement, + # it does not make sense to look at block equality (as energy won't be zero in cases that there is a difficult trade off) + if config.sel_next_dp == "all_metrics": + sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics = sorted(sim_dp_stat_ann_delta_energy_dict_all_metrics.items(), + key=lambda x: x[1]) + + if sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0][1] < -.0001: # a very small number + # if a better design (than the best exist), return + return sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0], True + else: + return sorted_sim_dp_stat_ann_delta_energy_dict_all_metrics[0], False + + + # two blocks are equal if they have the same generic instance name + # and have been scheduled the same tasks + def blocks_are_equal(block_1, block_2): + if not selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + return False + elif selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + # make sure tasks are not the same. + # this is to avoid scenarios where a block is improved (but it's generic name) equal to the + # next block bottleneck. Here we make sure tasks are different + block_1_tasks = [tsk.name for tsk in block_1.get_tasks_of_block()] + block_2_tasks = [tsk.name for tsk in block_1.get_tasks_of_block()] + task_diff = list(set(block_1_tasks) - set(block_2_tasks)) + return len(task_diff) == 0 + + # get the best_sim info + sim_dp = best_sim_dp_stat_so_far.dp + ex_dp = best_ex_dp_so_far + + best_sim_selected_metric, metric_prob_dict, best_sorted_metric_dir = self.select_metric(sim_dp) + best_sim_move_dir = self.select_dir(sim_dp, best_sim_selected_metric) + best_sim_selected_krnl, krnl_prob_dict = self.select_kernel(ex_dp, sim_dp, best_sim_selected_metric, best_sorted_metric_dir) + best_sim_selected_block, block_prob_dict = self.select_block_without_sync(sim_dp, best_sim_selected_krnl, best_sim_selected_metric) + + # sort the design base on distance + sorted_sim_dp_stat_ann_delta_energy_dict = sorted(sim_dp_stat_ann_delta_energy_dict.items(), key=lambda x: x[1]) + + #best_neighbour_stat, best_neighbour_delta_energy = sorted_sim_dp_stat_ann_delta_energy_dict[0] # here we can be smarter + if sorted_sim_dp_stat_ann_delta_energy_dict[0][1] < 0: + # if a better design (than the best exist), return + return sorted_sim_dp_stat_ann_delta_energy_dict[0], True + elif sorted_sim_dp_stat_ann_delta_energy_dict[0][1] == 0: + # if no better design + if len(sorted_sim_dp_stat_ann_delta_energy_dict[0]) == 1: + # if no better design (only one design means that our original design is the one) + return sorted_sim_dp_stat_ann_delta_energy_dict[0], False + else: + # filter out the designs which hasn't seen a distance improvement + sim_dp_to_select_from = [] + for sim_dp_stat, energy in sorted_sim_dp_stat_ann_delta_energy_dict: + if energy == 0: + sim_dp_to_select_from.append((sim_dp_stat, energy)) + + + designs_to_consider = [] + for sim_dp_stat, energy in sim_dp_to_select_from: + sim_dp = sim_dp_stat.dp + ex_dp = sim_stat_ex_dp_dict[sim_dp_stat] + selected_metric, metric_prob_dict, sorted_metric_dir = self.select_metric(sim_dp) + move_dir = self.select_dir(sim_dp, selected_metric) + selected_krnl, krnl_prob_dict = self.select_kernel(ex_dp, sim_dp, selected_metric, sorted_metric_dir) + selected_block, block_prob_dict = self.select_block_without_sync(sim_dp, selected_krnl, + selected_metric) + if not selected_krnl.get_task_name() == best_sim_selected_krnl.get_task_name(): + designs_to_consider.append((sim_dp_stat, energy)) + elif not blocks_are_equal(selected_block, best_sim_selected_block): + #elif not selected_block.get_generic_instance_name() == best_sim_selected_block.get_generic_instance_name(): + designs_to_consider.append((sim_dp_stat, energy)) + + if len(designs_to_consider) == 0: + return sim_dp_to_select_from[0], False + else: + return designs_to_consider[0], True # can be smarter here + + + # use simulated annealing to pick the next design(s). + # Use this link to understand simulated annealing (SA) http://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/we /glossary/anneal.html + # cur_temp: current temperature for simulated annealing + def moos_greedy_design_selection(self, sim_stat_ex_dp_dict, sim_dp_stat_list, best_ex_dp_so_far, best_sim_dp_so_far_stats, cur_temp): + def get_kernel_not_to_consider(krnels_not_to_consider, cur_best_move_applied, random_move_applied): + if cur_best_move_applied == None: # only none for the first iteration + move_applied = random_move_applied + else: + move_applied = cur_best_move_applied + if move_applied == None: + return None + + krnl_prob_dict_sorted = move_applied.krnel_prob_dict_sorted + for krnl, prob in krnl_prob_dict_sorted: + if krnl.get_task_name() in krnels_not_to_consider: + continue + return krnl.get_task_name() + + + + # get the kernel of interest using this for now to collect cached designs + best_sim_selected_metric, metric_prob_dict,best_sorted_metric_dir = self.select_metric(best_sim_dp_so_far_stats.dp) + best_sim_move_dir = self.select_dir(best_sim_dp_so_far_stats.dp, best_sim_selected_metric) + best_sim_selected_krnl, _, _= self.select_kernel(best_ex_dp_so_far, best_sim_dp_so_far_stats.dp, best_sim_selected_metric, best_sorted_metric_dir) + if best_sim_selected_krnl.get_task_name() not in self.recently_cached_designs.keys(): + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()] = [] + + + # get the worse case cost for normalizing the cost when calculating the distance + best_cost = min([sim_dp.get_system_complex_metric("cost") for sim_dp in (sim_dp_stat_list + [best_sim_dp_so_far_stats])]) + self.database.set_ideal_metric_value("cost", "glass", best_cost) + + # find if any of the new designs meet the budget + new_designs_meeting_budget = [] # designs that are meeting the budget + for sim_dp_stat in sim_dp_stat_list: + if sim_dp_stat.fits_budget(1): + new_designs_meeting_budget.append(sim_dp_stat) + + new_designs_meeting_budget_with_dram = [] # designs that are meeting the budget + for sim_dp_stat in sim_dp_stat_list: + if sim_dp_stat.fits_budget(1): + ex_dp = sim_stat_ex_dp_dict[sim_dp_stat] + if ex_dp.has_system_bus(): + new_designs_meeting_budget_with_dram.append(sim_dp_stat) + dram_fixed = False + if len(new_designs_meeting_budget_with_dram) > 0 and not self.dram_feasibility_check_pass(best_ex_dp_so_far): + dram_fixed = True + + + # find each design's simulated annealing Energy difference with the best design's energy + # if any of the designs meet the budget or it's a cleanup iteration, include cost in distance calculation. + # note that when we compare, we need to use the same dist_to_goal calculation, hence + # ann_energy_best_dp_so_far needs to use the same calculation + metric_to_target , metric_prob_dict, sorted_metric_dir = self.select_metric(best_sim_dp_so_far_stats.dp) + include_cost_in_distance = best_sim_dp_so_far_stats.fits_budget(1) or (len(new_designs_meeting_budget) > 0) or self.is_cleanup_iter() or (len(new_designs_meeting_budget_with_dram)>0) + if include_cost_in_distance: + ann_energy_best_dp_so_far = best_sim_dp_so_far_stats.dist_to_goal(["cost", "latency", "power", "area"], + "eliminate") + ann_energy_best_dp_so_far_all_metrics = best_sim_dp_so_far_stats.dist_to_goal(["cost", "latency", "power", "area"], + "eliminate") + else: + ann_energy_best_dp_so_far = best_sim_dp_so_far_stats.dist_to_goal([metric_to_target], "dampen") + ann_energy_best_dp_so_far_all_metrics = best_sim_dp_so_far_stats.dist_to_goal(["power", "area", "latency"], + "dampen") + sim_dp_stat_ann_delta_energy_dict = {} + sim_dp_stat_ann_delta_energy_dict_all_metrics = {} + # deleteee the following debugging lines + if config.print_info_regularly: + print("--------%%%%%%%%%%%---------------") + print("--------%%%%%%%%%%%---------------") + print("first the best design from the previous iteration") + print(" des" + " latency:" + str(best_sim_dp_so_far_stats.get_system_complex_metric("latency"))) + print(" des" + " power:" + str( + best_sim_dp_so_far_stats.get_system_complex_metric("power"))) + print("energy :" + str(ann_energy_best_dp_so_far)) + + + + sim_dp_to_look_at = [] # which designs to look at. + # only look at the designs that meet the budget (if any), basically prioritize these designs first + if len(new_designs_meeting_budget_with_dram) > 0: + sim_dp_to_look_at = new_designs_meeting_budget_with_dram + elif len(new_designs_meeting_budget) > 0: + sim_dp_to_look_at = new_designs_meeting_budget + else: + sim_dp_to_look_at = sim_dp_stat_list + + for sim_dp_stat in sim_dp_to_look_at: + if include_cost_in_distance: + sim_dp_stat_ann_delta_energy_dict[sim_dp_stat] = sim_dp_stat.dist_to_goal( + ["cost", "latency", "power", "area"], "eliminate") - ann_energy_best_dp_so_far + sim_dp_stat_ann_delta_energy_dict_all_metrics[sim_dp_stat] = sim_dp_stat.dist_to_goal( + ["cost", "latency", "power", "area"], "eliminate") - ann_energy_best_dp_so_far_all_metrics + else: + new_design_energy = sim_dp_stat.dist_to_goal([metric_to_target], "dampen") + sim_dp_stat_ann_delta_energy_dict[sim_dp_stat] = new_design_energy - ann_energy_best_dp_so_far + new_design_energy_all_metrics = sim_dp_stat.dist_to_goal(["power", "latency", "area"], "dampen") + sim_dp_stat_ann_delta_energy_dict_all_metrics[sim_dp_stat] = new_design_energy_all_metrics - ann_energy_best_dp_so_far_all_metrics + + # changing the seed for random selection + if config.DEBUG_FIX: random.seed(0) + else: time.sleep(.00001), random.seed(datetime.now().microsecond) + + result, design_improved = self.find_best_design(sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, + sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_so_far_stats, best_ex_dp_so_far) + best_neighbour_stat, best_neighbour_delta_energy = result + + if config.print_info_regularly: + print("all the designs tried") + for el, energy in sim_dp_stat_ann_delta_energy_dict_all_metrics.items(): + print("----------------") + sim_dp_ = el.dp + if not sim_dp_.move_applied == None: + sim_dp_.move_applied.print_info() + print("energy" + str(energy)) + print("design's latency: " + str(el.get_system_complex_metric("latency"))) + print("design's power: " + str(el.get_system_complex_metric("power"))) + print("design's area: " + str(el.get_system_complex_metric("area"))) + print("design's sub area: " + str(el.get_system_complex_area_stacked_dram())) + + # if any negative (desired move) value is detected or there is a design in the new batch + # that meet the budget, but the previous best design didn't, we have at least one improved solution + found_an_improved_solution = (len(new_designs_meeting_budget)>0 and not(best_sim_dp_so_far_stats).fits_budget(1)) or design_improved or dram_fixed + + + # for debugging. delete later + if (len(new_designs_meeting_budget)>0 and not(best_sim_dp_so_far_stats).fits_budget(1)): + print("what") + + if not found_an_improved_solution: + # avoid not improving + self.krnel_stagnation_ctr +=1 + self.des_stag_ctr += 1 + if self.krnel_stagnation_ctr > config.max_krnel_stagnation_ctr: + self.krnel_rnk_to_consider = min(self.krnel_rnk_to_consider + 1, len(best_sim_dp_so_far_stats.get_kernels()) -1) + krnel_not_to_consider = get_kernel_not_to_consider(self.krnels_not_to_consider, best_sim_dp_so_far_stats.dp.move_applied, sim_dp_to_look_at[-1].dp.move_applied) + if not krnel_not_to_consider == None: + self.krnels_not_to_consider.append(krnel_not_to_consider) + #self.krnel_stagnation_ctr = 0 + #self.recently_seen_design_ctr = 0 + elif best_neighbour_stat.dp.dp_rep.get_hardware_graph().get_SOC_design_code() in self.recently_cached_designs[best_sim_selected_krnl.get_task_name()] and False: + # avoid circular exploration + self.recently_seen_design_ctr += 1 + self.des_stag_ctr += 1 + if self.recently_seen_design_ctr > config.max_recently_seen_design_ctr: + self.krnel_rnk_to_consider = min(self.krnel_rnk_to_consider + 1, + len(best_sim_dp_so_far_stats.get_kernels()) - 1) + self.krnel_stagnation_ctr = 0 + #self.recently_seen_design_ctr = 0 + else: + self.krnel_stagnation_ctr = max(0, self.krnel_stagnation_ctr -1) + if self.krnel_stagnation_ctr == 0: + if not len(self.krnels_not_to_consider) == 0: + self.krnels_not_to_consider = self.krnels_not_to_consider[:-1] + self.krnel_rnk_to_consider = max(0, self.krnel_rnk_to_consider - 1) + self.cleanup_ctr +=1 + self.des_stag_ctr = 0 + self.recently_seen_design_ctr = 0 + + # initialize selected_sim_dp + selected_sim_dp = best_sim_dp_so_far_stats.dp + if found_an_improved_solution: + selected_sim_dp = best_neighbour_stat.dp + else: + try: + #if math.e**(best_neighbour_delta_energy/max(cur_temp, .001)) < random.choice(range(0, 1)): + # selected_sim_dp = best_neighbour_stat.dp + if random.choice(range(0, 1)) < math.e**(best_neighbour_delta_energy/max(cur_temp, .001)): + selected_sim_dp = best_neighbour_stat.dp + except: + selected_sim_dp = best_neighbour_stat.dp + + # cache the best design + if len(self.recently_cached_designs[best_sim_selected_krnl.get_task_name()]) < config.recently_cached_designs_queue_size: + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()].append(selected_sim_dp.dp_rep.get_hardware_graph().get_SOC_design_code()) + else: + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()][self.population_generation_cnt%config.recently_cached_designs_queue_size] = selected_sim_dp.dp_rep.get_hardware_graph().get_SOC_design_code() + + return selected_sim_dp, found_an_improved_solution + + # use simulated annealing to pick the next design(s). + # Use this link to understand simulated annealing (SA) http://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/we /glossary/anneal.html + # cur_temp: current temperature for simulated annealing + def SA_design_selection(self, sim_stat_ex_dp_dict, sim_dp_stat_list, best_ex_dp_so_far, best_sim_dp_so_far_stats, cur_temp): + + def get_kernel_not_to_consider(krnels_not_to_consider, cur_best_move_applied, random_move_applied): + if cur_best_move_applied == None: # only none for the first iteration + move_applied = random_move_applied + else: + move_applied = cur_best_move_applied + if move_applied == None: + return None + + krnl_prob_dict_sorted = move_applied.krnel_prob_dict_sorted + for krnl, prob in krnl_prob_dict_sorted: + if krnl.get_task_name() in krnels_not_to_consider: + continue + return krnl.get_task_name() + + + # get the kernel of interest using this for now to collect cached designs + best_sim_selected_metric, metric_prob_dict,best_sorted_metric_dir = self.select_metric(best_sim_dp_so_far_stats.dp) + best_sim_move_dir = self.select_dir(best_sim_dp_so_far_stats.dp, best_sim_selected_metric) + best_sim_selected_krnl, _, _= self.select_kernel(best_ex_dp_so_far, best_sim_dp_so_far_stats.dp, best_sim_selected_metric, best_sorted_metric_dir) + if best_sim_selected_krnl.get_task_name() not in self.recently_cached_designs.keys(): + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()] = [] + + + # get the worse case cost for normalizing the cost when calculating the distance + best_cost = min([sim_dp.get_system_complex_metric("cost") for sim_dp in (sim_dp_stat_list + [best_sim_dp_so_far_stats])]) + self.database.set_ideal_metric_value("cost", "glass", best_cost) + + # find if any of the new designs meet the budget + new_designs_meeting_budget = [] # designs that are meeting the budget + for sim_dp_stat in sim_dp_stat_list: + if sim_dp_stat.fits_budget(1): + new_designs_meeting_budget.append(sim_dp_stat) + + new_designs_meeting_budget_with_dram = [] # designs that are meeting the budget + for sim_dp_stat in sim_dp_stat_list: + if sim_dp_stat.fits_budget(1): + ex_dp = sim_stat_ex_dp_dict[sim_dp_stat] + if ex_dp.has_system_bus(): + new_designs_meeting_budget_with_dram.append(sim_dp_stat) + dram_fixed = False + if len(new_designs_meeting_budget_with_dram) > 0 and not self.dram_feasibility_check_pass(best_ex_dp_so_far): + dram_fixed = True + + + # find each design's simulated annealing Energy difference with the best design's energy + # if any of the designs meet the budget or it's a cleanup iteration, include cost in distance calculation. + # note that when we compare, we need to use the same dist_to_goal calculation, hence + # ann_energy_best_dp_so_far needs to use the same calculation + metric_to_target , metric_prob_dict, sorted_metric_dir = self.select_metric(best_sim_dp_so_far_stats.dp) + include_cost_in_distance = best_sim_dp_so_far_stats.fits_budget(1) or (len(new_designs_meeting_budget) > 0) or self.is_cleanup_iter() or (len(new_designs_meeting_budget_with_dram)>0) + if include_cost_in_distance: + ann_energy_best_dp_so_far = best_sim_dp_so_far_stats.dist_to_goal(["cost", "latency", "power", "area"], + "eliminate") + ann_energy_best_dp_so_far_all_metrics = best_sim_dp_so_far_stats.dist_to_goal(["cost", "latency", "power", "area"], + "eliminate") + else: + ann_energy_best_dp_so_far = best_sim_dp_so_far_stats.dist_to_goal([metric_to_target], "dampen") + ann_energy_best_dp_so_far_all_metrics = best_sim_dp_so_far_stats.dist_to_goal(["power", "area", "latency"], + "dampen") + sim_dp_stat_ann_delta_energy_dict = {} + sim_dp_stat_ann_delta_energy_dict_all_metrics = {} + # deleteee the following debugging lines + print("--------%%%%%%%%%%%---------------") + print("--------%%%%%%%%%%%---------------") + print("first the best design from the previous iteration") + print(" des" + " latency:" + str(best_sim_dp_so_far_stats.get_system_complex_metric("latency"))) + print(" des" + " power:" + str( + best_sim_dp_so_far_stats.get_system_complex_metric("power"))) + print("energy :" + str(ann_energy_best_dp_so_far)) + + + + sim_dp_to_look_at = [] # which designs to look at. + # only look at the designs that meet the budget (if any), basically prioritize these designs first + if len(new_designs_meeting_budget_with_dram) > 0: + sim_dp_to_look_at = new_designs_meeting_budget_with_dram + elif len(new_designs_meeting_budget) > 0: + sim_dp_to_look_at = new_designs_meeting_budget + else: + sim_dp_to_look_at = sim_dp_stat_list + + for sim_dp_stat in sim_dp_to_look_at: + if include_cost_in_distance: + sim_dp_stat_ann_delta_energy_dict[sim_dp_stat] = sim_dp_stat.dist_to_goal( + ["cost", "latency", "power", "area"], "eliminate") - ann_energy_best_dp_so_far + sim_dp_stat_ann_delta_energy_dict_all_metrics[sim_dp_stat] = sim_dp_stat.dist_to_goal( + ["cost", "latency", "power", "area"], "eliminate") - ann_energy_best_dp_so_far_all_metrics + else: + new_design_energy = sim_dp_stat.dist_to_goal([metric_to_target], "dampen") + sim_dp_stat_ann_delta_energy_dict[sim_dp_stat] = new_design_energy - ann_energy_best_dp_so_far + new_design_energy_all_metrics = sim_dp_stat.dist_to_goal(["power", "latency", "area"], "dampen") + sim_dp_stat_ann_delta_energy_dict_all_metrics[sim_dp_stat] = new_design_energy_all_metrics - ann_energy_best_dp_so_far_all_metrics + + # changing the seed for random selection + if config.DEBUG_FIX: random.seed(0) + else: time.sleep(.00001), random.seed(datetime.now().microsecond) + + result, design_improved = self.find_best_design(sim_stat_ex_dp_dict, sim_dp_stat_ann_delta_energy_dict, + sim_dp_stat_ann_delta_energy_dict_all_metrics, best_sim_dp_so_far_stats, best_ex_dp_so_far) + best_neighbour_stat, best_neighbour_delta_energy = result + + print("all the designs tried") + for el, energy in sim_dp_stat_ann_delta_energy_dict_all_metrics.items(): + print("----------------") + sim_dp_ = el.dp + if not sim_dp_.move_applied == None: + sim_dp_.move_applied.print_info() + print("energy" + str(energy)) + print("design's latency: " + str(el.get_system_complex_metric("latency"))) + print("design's power: " + str(el.get_system_complex_metric("power"))) + print("design's area: " + str(el.get_system_complex_metric("area"))) + print("design's sub area: " + str(el.get_system_complex_area_stacked_dram())) + + # if any negative (desired move) value is detected or there is a design in the new batch + # that meet the budget, but the previous best design didn't, we have at least one improved solution + found_an_improved_solution = (len(new_designs_meeting_budget)>0 and not(best_sim_dp_so_far_stats).fits_budget(1)) or design_improved or dram_fixed + + + # for debugging. delete later + if (len(new_designs_meeting_budget)>0 and not(best_sim_dp_so_far_stats).fits_budget(1)): + print("what") + + if not found_an_improved_solution: + # avoid not improving + self.krnel_stagnation_ctr +=1 + self.des_stag_ctr += 1 + if self.krnel_stagnation_ctr > config.max_krnel_stagnation_ctr: + self.krnel_rnk_to_consider = min(self.krnel_rnk_to_consider + 1, len(best_sim_dp_so_far_stats.get_kernels()) -1) + krnel_not_to_consider = get_kernel_not_to_consider(self.krnels_not_to_consider, best_sim_dp_so_far_stats.dp.move_applied, sim_dp_to_look_at[-1].dp.move_applied) + if not krnel_not_to_consider == None: + self.krnels_not_to_consider.append(krnel_not_to_consider) + #self.krnel_stagnation_ctr = 0 + #self.recently_seen_design_ctr = 0 + elif best_neighbour_stat.dp.dp_rep.get_hardware_graph().get_SOC_design_code() in self.recently_cached_designs[best_sim_selected_krnl.get_task_name()] and False: + # avoid circular exploration + self.recently_seen_design_ctr += 1 + self.des_stag_ctr += 1 + if self.recently_seen_design_ctr > config.max_recently_seen_design_ctr: + self.krnel_rnk_to_consider = min(self.krnel_rnk_to_consider + 1, + len(best_sim_dp_so_far_stats.get_kernels()) - 1) + self.krnel_stagnation_ctr = 0 + #self.recently_seen_design_ctr = 0 + else: + self.krnel_stagnation_ctr = max(0, self.krnel_stagnation_ctr -1) + if self.krnel_stagnation_ctr == 0: + if not len(self.krnels_not_to_consider) == 0: + self.krnels_not_to_consider = self.krnels_not_to_consider[:-1] + self.krnel_rnk_to_consider = max(0, self.krnel_rnk_to_consider - 1) + self.cleanup_ctr +=1 + self.des_stag_ctr = 0 + self.recently_seen_design_ctr = 0 + + # initialize selected_sim_dp + selected_sim_dp = best_sim_dp_so_far_stats.dp + if found_an_improved_solution: + selected_sim_dp = best_neighbour_stat.dp + else: + try: + #if math.e**(best_neighbour_delta_energy/max(cur_temp, .001)) < random.choice(range(0, 1)): + # selected_sim_dp = best_neighbour_stat.dp + if random.choice(range(0, 1)) < math.e**(best_neighbour_delta_energy/max(cur_temp, .001)): + selected_sim_dp = best_neighbour_stat.dp + + except: + selected_sim_dp = best_neighbour_stat.dp + + # cache the best design + if len(self.recently_cached_designs[best_sim_selected_krnl.get_task_name()]) < config.recently_cached_designs_queue_size: + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()].append(selected_sim_dp.dp_rep.get_hardware_graph().get_SOC_design_code()) + else: + self.recently_cached_designs[best_sim_selected_krnl.get_task_name()][self.population_generation_cnt%config.recently_cached_designs_queue_size] = selected_sim_dp.dp_rep.get_hardware_graph().get_SOC_design_code() + + return selected_sim_dp, found_an_improved_solution + + def find_design_scalarized_value_from_moos_perspective(self, sim, lambdas): + sim_metric_values = {} + value = [] + for metric_name in config.budgetted_metrics: + for type, id in sim.get_designs_SOCs(): + if metric_name == "latency": + metric_val = sum(list(sim.dp_stats.get_SOC_metric_value(metric_name, type, id).values())) + else: + metric_val = sim.dp_stats.get_SOC_metric_value(metric_name, type, id) + + value.append(Decimal(metric_val)*lambdas[metric_name]) + + + #return max(value) + return sum(value) + + # ------------------------------ + # Functionality: + # select the next best design (from the sorted dp) + # Variables + # ex_sim_dp_dict: example_simulate_design_point_list. List of designs to pick from. + # ------------------------------ + def sel_start_dp_moos(self, ex_sim_dp_dict, best_sim_dp_so_far, best_ex_dp_so_far, lambda_list): + # convert to stats + sim_dp_list = list(ex_sim_dp_dict.values()) + sim_dp_stat_list = [sim_dp.dp_stats for sim_dp in sim_dp_list] + sim_stat_ex_dp_dict = {} + for k, v in ex_sim_dp_dict.items(): + sim_stat_ex_dp_dict[v.dp_stats] = k + + + # find the ones that fit the expanded budget (note that budget radius shrinks) + sim_scalarized_value = {} + for ex, sim in ex_sim_dp_dict.items(): + value = self.find_design_scalarized_value_from_moos_perspective(sim, lambda_list) + sim_scalarized_value[sim] = value + + min_scalarized_value = float('inf') + min_sim = "" + for sim,value in sim_scalarized_value.items(): + if value < min_scalarized_value: + min_sim = sim + min_ex = sim_stat_ex_dp_dict[sim.dp_stats] + min_scalarized_value = value + + if min_sim == "": + print("some thing went wrong. should have at least one minimum") + return min_ex, min_sim + + + # extract the design + for key, val in ex_sim_dp_dict.items(): + key.sanity_check() + if val == selected_sim_dp: + selected_ex_dp = key + break + + # generate verification data + if found_improved_solution and config.RUN_VERIFICATION_PER_IMPROVMENT: + self.gen_verification_data(selected_sim_dp, selected_ex_dp) + return selected_ex_dp, selected_sim_dp + + + + # ------------------------------ + # Functionality: + # select the next best design (from the sorted dp) + # Variables + # ex_sim_dp_dict: example_simulate_design_point_list. List of designs to pick from. + # ------------------------------ + def sel_next_dp(self, ex_sim_dp_dict, best_sim_dp_so_far, best_ex_dp_so_far, cur_temp): + # convert to stats + sim_dp_list = list(ex_sim_dp_dict.values()) + sim_dp_stat_list = [sim_dp.dp_stats for sim_dp in sim_dp_list] + sim_stat_ex_dp_dict = {} + for k, v in ex_sim_dp_dict.items(): + sim_stat_ex_dp_dict[v.dp_stats] = k + + + # find the ones that fit the expanded budget (note that budget radius shrinks) + selected_sim_dp, found_improved_solution = self.SA_design_selection(sim_stat_ex_dp_dict, sim_dp_stat_list, + best_ex_dp_so_far, best_sim_dp_so_far.dp_stats, + cur_temp) + + self.found_any_improvement = self.found_any_improvement or found_improved_solution + + if not found_improved_solution: + selected_sim_dp = self.so_far_best_sim_dp + selected_ex_dp = self.so_far_best_ex_dp + else: + # extract the design + for key, val in ex_sim_dp_dict.items(): + key.sanity_check() + if val == selected_sim_dp: + selected_ex_dp = key + break + + # generate verification data + if found_improved_solution and config.RUN_VERIFICATION_PER_IMPROVMENT: + self.gen_verification_data(selected_sim_dp, selected_ex_dp) + return selected_ex_dp, selected_sim_dp + + # ------------------------------ + # Functionality: + # simulate one design. + # Variables + # ex_dp: example design point. Design point to simulate. + # database: hardware/software data base to simulated based off of. + # ------------------------------ + def sim_one_design(self, ex_dp, database): + if config.simulation_method == "power_knobs": + sim_dp = self.dh.convert_to_sim_des_point(ex_dp) + power_knob_sim_dp = self.dh.convert_to_sim_des_point(ex_dp) + OSA = OSASimulator(sim_dp, database, power_knob_sim_dp) + else: + sim_dp = self.dh.convert_to_sim_des_point(ex_dp) + # Simulator initialization + OSA = OSASimulator(sim_dp, database) # change + + # Does the actual simulation + t = time.time() + OSA.simulate() + sim_time = time.time() - t + + # profile info + sim_dp.set_population_generation_cnt(self.population_generation_cnt) + sim_dp.set_population_observed_number(self.population_observed_ctr) + sim_dp.set_depth_number(self.SA_current_depth) + sim_dp.set_simulation_time(sim_time) + + #print("sim time" + str(sim_time)) + #exit(0) + return sim_dp + + # ------------------------------ + # Functionality: + # Sampling from the task distribution. This is used for jitter incorporation. + # Variables: + # ex_dp: example design point. + # ------------------------------ + def generate_sample(self, ex_dp, hw_sampling): + #new_ex_dp = copy.deepcopy(ex_dp) + gc.disable() + new_ex_dp = cPickle.loads(cPickle.dumps(ex_dp, -1)) + gc.enable() + new_ex_dp.sample_hardware_graph(hw_sampling) + return new_ex_dp + + + + + def transform_to_most_inferior_design(self, ex_dp:ExDesignPoint): + new_ex_dp = cPickle.loads(cPickle.dumps(ex_dp, -1)) + move_to_try = move("swap", "swap", "irrelevant", "-1", "latency", "", "", "") + all_blocks = new_ex_dp.get_blocks() + for block in all_blocks: + self.dh.unload_read_mem(new_ex_dp) # unload memories + if not block.type == "ic": + self.dh.unload_buses(new_ex_dp) # unload buses + else: + self.dh.unload_read_buses(new_ex_dp) # unload buses + + move_to_try.set_ref_block(block) + # get immediate superior/inferior block (based on the desired direction) + most_inferior_block = self.dh.get_most_inferior_block(block, block.get_tasks_of_block()) + move_to_try.set_dest_block(most_inferior_block) + move_to_try.set_customization_type(block, most_inferior_block) + move_to_try.set_tasks(block.get_tasks_of_block()) + self.dh.unload_read_mem(new_ex_dp) # unload read memories + move_to_try.validity_check() # call after unload rad mems, because we need to check the scenarios where + # task is unloaded from the mem, but was decided to be migrated/swapped + new_ex_dp_res, succeeded = self.dh.apply_move([new_ex_dp,""], move_to_try) + #self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp_res) # loading the tasks on to memory and ic + new_ex_dp_res.hardware_graph.pipe_design() + new_ex_dp_res.sanity_check() + new_ex_dp = new_ex_dp_res + #cPickle.loads(cPickle.dumps(new_ex_dp_res, -1)) + #self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp) # loading the tasks on to memory and ic + + + self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp) # loading the tasks on to memory and ic + return new_ex_dp + + def transform_to_most_inferior_design_before_loop_unrolling(self, ex_dp: ExDesignPoint): + new_ex_dp = cPickle.loads(cPickle.dumps(ex_dp, -1)) + move_to_try = move("swap", "swap", "irrelevant", "-1", "latency", "", "", "") + all_blocks = new_ex_dp.get_blocks() + for block in all_blocks: + self.dh.unload_read_mem(new_ex_dp) # unload memories + if not block.type == "ic": + self.dh.unload_buses(new_ex_dp) # unload buses + else: + self.dh.unload_read_buses(new_ex_dp) # unload buses + + move_to_try.set_ref_block(block) + # get immediate superior/inferior block (based on the desired direction) + most_inferior_block = self.dh.get_most_inferior_block_before_unrolling(block, block.get_tasks_of_block()) + #most_inferior_block = self.dh.get_most_inferior_block(block, block.get_tasks_of_block()) + move_to_try.set_dest_block(most_inferior_block) + move_to_try.set_customization_type(block, most_inferior_block) + move_to_try.set_tasks(block.get_tasks_of_block()) + self.dh.unload_read_mem(new_ex_dp) # unload read memories + move_to_try.validity_check() # call after unload rad mems, because we need to check the scenarios where + # task is unloaded from the mem, but was decided to be migrated/swapped + new_ex_dp_res, succeeded = self.dh.apply_move([new_ex_dp, ""], move_to_try) + # self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp_res) # loading the tasks on to memory and ic + new_ex_dp_res.hardware_graph.pipe_design() + new_ex_dp_res.sanity_check() + new_ex_dp = new_ex_dp_res + # cPickle.loads(cPickle.dumps(new_ex_dp_res, -1)) + # self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp) # loading the tasks on to memory and ic + + self.dh.load_tasks_to_read_mem_and_ic(new_ex_dp) # loading the tasks on to memory and ic + return new_ex_dp + + def single_out_workload(self,ex_dp, database, workload, workload_tasks): + new_ex_dp = cPickle.loads(cPickle.dumps(ex_dp, -1)) + database_ = cPickle.loads(cPickle.dumps(database, -1)) + for block in new_ex_dp.get_blocks(): + for dir in ["loop_back","write","read"]: + tasks= block.get_tasks_of_block_by_dir(dir) + for task in tasks: + if task.get_name() not in workload_tasks: + block.unload((task,dir)) + + tasks = new_ex_dp.get_tasks() + for task in tasks: + children = task.get_children()[:] + parents = task.get_parents()[:] + for child in children: + if child.get_name() not in workload_tasks: + task.remove_child(child) + for parent in parents: + if parent.get_name() not in workload_tasks: + task.remove_parent(parent) + + database_.set_workloads_last_task({workload: database_.db_input.workloads_last_task[workload]}) + return new_ex_dp, database_ + + # ------------------------------ + # Functionality: + # Evaluate the design. 1. simulate 2. collect (profile) data. + # Variables: + # ex_dp: example design point. + # database: database containing hardware/software modeled characteristics. + # ------------------------------ + def eval_design(self, ex_dp:ExDesignPoint, database): + #start = time.time() + # according to config singular runs + if config.eval_mode == "singular": + print("this mode is deprecated. just use statistical. singular is simply a special case") + exit(0) + return self.sim_one_design(ex_dp, database) # evaluation the design directly + elif config.eval_mode == "statistical": + # generate a population (geneate_sample), evaluate them and reduce to some statistical indicator + ex_dp_pop_sample = [self.generate_sample(ex_dp, database.hw_sampling) for i in range(0, self.database.hw_sampling["population_size"])] # population sample + ex_dp.get_tasks()[0].task_id_for_debugging_static += 1 + sim_dp_pop_sample = list(map(lambda ex_dp_: self.sim_one_design(ex_dp_, database), ex_dp_pop_sample)) # evaluate the population sample + + # collect profiling information + sim_dp_statistical = SimDesignPointContainer(sim_dp_pop_sample, database, config.statistical_reduction_mode) + #print("time is:" + str(time.time() -start)) + return sim_dp_statistical + else: + print("mode" + config.eval_mode + " is not defined for eval design") + + # ------------------------------ + # Functionality: + # generate verification (platform architect digestible) designs. + # Variables: + # sim_dp: simulated design point. + # ------------------------------ + def gen_verification_data(self, sim_dp_, ex_dp_): + #from data_collection.FB_private.verification_utils.PA_generation.PA_generators import * + import_ver = importlib.import_module("data_collection.FB_private.verification_utils.PA_generation.PA_generators") + # iterate till you can make a directory + while True: + date_time = datetime.now().strftime('%m-%d_%H-%M_%S') + #result_folder = os.path.join(self.result_dir, "data_per_design", + # date_time+"_"+str(self.name_ctr)) + # one for PA data collection + result_folder = os.path.join(self.result_dir+"/../../", "data_per_design", + date_time+"_"+str(self.name_ctr)) + + if not os.path.isdir(result_folder): + os.makedirs(result_folder) + collection_ctr = self.name_ctr # used to realize which results to compare + break + + ex_with_PA = [] # + pa_ver_obj = import_ver.PAVerGen() # initialize a PA generator + # make all the combinations + knobs_list, knob_order = pa_ver_obj.gen_all_PA_knob_combos(import_ver.PA_knobs_to_explore) # generate knob combinations + # for different PA designs. Since PA has extra knobs, we'd like sweep this knobs for verification purposes. + knob_ctr = 0 + # Iterate though the knob combos and generate a (PA digestible) design accordingly + for knobs in knobs_list: + result_folder_for_knob = os.path.join(result_folder, "PA_knob_ctr_"+str(knob_ctr)) + for sim_dp in sim_dp_.get_design_point_list(): + sim_dp.reset_PA_knobs() + sim_dp.update_ex_id(date_time+"_"+str(collection_ctr)+"_" + str(self.name_ctr)) + sim_dp.update_FARSI_ex_id(date_time+"_"+str(collection_ctr)) + sim_dp.update_PA_knob_ctr_id(str(knob_ctr)) + sim_dp.update_PA_knobs(knobs, knob_order, import_ver.auto_tuning_knobs) + PA_result_folder = os.path.join(result_folder_for_knob, str(sim_dp.id)) + os.makedirs(PA_result_folder) + + # dump data for bus-memory data with connection (used for bw calculation) + sim_dp.dump_mem_bus_connection_bw(PA_result_folder) # write the results into a file + # initialize and do some clean up + #vis_hardware.vis_hardware(sim_dp, config.hw_graphing_mode, PA_result_folder) + sim_dp.dp_stats.dump_stats(PA_result_folder) + if config.VIS_SIM_PROG: vis_sim.plot_sim_data(sim_dp.dp_stats, sim_dp, PA_result_folder) + block_names = [block.instance_name for block in sim_dp.hardware_graph.blocks] + vis_hardware.vis_hardware(sim_dp, config.hw_graphing_mode, PA_result_folder) + pa_obj = import_ver.PAGen(self.database, self.database.db_input.proj_name, sim_dp, PA_result_folder, config.sw_model) + + pa_obj.gen_all() # generate the PA digestible design + sim_dp.dump_props(PA_result_folder) # write the results into a file + # pickle the results for (out of run) verifications. + ex_dp_pickled_file = open(os.path.join(PA_result_folder, "ex_dp_pickled.txt"), "wb") + dill.dump(ex_dp_, ex_dp_pickled_file) + ex_dp_pickled_file.close() + + database_pickled_file = open(os.path.join(PA_result_folder, "database_pickled.txt"), "wb") + dill.dump(self.database, database_pickled_file) + database_pickled_file.close() + + sim_dp_pickled_file = open(os.path.join(PA_result_folder, "sim_dp_pickled.txt"), "wb") + dill.dump(sim_dp, sim_dp_pickled_file) + sim_dp_pickled_file.close() + self.name_ctr += 1 + knob_ctr += 1 + + + + + # ------------------------------ + # Functionality: + # generate one neighbour and evaluate it. + # Variables: + # des_tup: starting point design point tuple (design point, simulated design point) + # ------------------------------ + #@profile + def gen_neigh_and_eval(self, des_tup): + # "delete this later" + print("------ depth ------") + # generate on neighbour + move_strt_time = time.time() + ex_dp, move_to_try,total_trans_cnt = self.gen_one_neigh(des_tup) + move_end_time = time.time() + move_to_try.set_generation_time(move_end_time- move_strt_time) + + # generate a code for the design (that specifies the topology, mapping and scheduling). + # look into cache and see if this design has been seen before. If so, just use the + # cached value, other wise just use the sim from cache + design_unique_code = ex_dp.get_hardware_graph().get_SOC_design_code() # cache index + if move_to_try.get_transformation_name() == "identity" or not move_to_try.is_valid(): + # if nothing has changed, just copy the sim from before + sim_dp = des_tup[1] + elif design_unique_code not in self.cached_SOC_sim.keys(): + self.population_observed_ctr += 1 + sim_dp = self.eval_design(ex_dp, self.database) # evaluate the designs + #if config.cache_seen_designs: # this seems to be slower than just simulation, because of deepcopy + # self.cached_SOC_sim[design_unique_code] = (ex_dp, sim_dp) + else: + ex_dp = self.cached_SOC_sim[design_unique_code][0] + sim_dp = self.cached_SOC_sim[design_unique_code][1] + + # collect the moves for debugging/visualization + if config.DEBUG_MOVE: + if (self.population_generation_cnt % config.vis_reg_ctr_threshold) == 0 and self.SA_current_mini_breadth == 0: + self.move_profile.append(move_to_try) # for debugging + self.last_move = move_to_try + sim_dp.set_move_applied(move_to_try) + + # visualization and verification + if config.VIS_GR_PER_GEN: + vis_hardware.vis_hardware(sim_dp.get_dp_rep()) + if config.RUN_VERIFICATION_PER_GEN or \ + (config.RUN_VERIFICATION_PER_NEW_CONFIG and + not(sim_dp.dp.get_hardware_graph().get_SOC_design_code() in self.seen_SOC_design_codes)): + self.gen_verification_data(sim_dp, ex_dp) + self.seen_SOC_design_codes.append(sim_dp.dp.get_hardware_graph().get_SOC_design_code()) + + + if not sim_dp.move_applied == None and config.print_info_regularly: + sim_dp.move_applied.print_info() + print("design's latency: " + str(sim_dp.dp_stats.get_system_complex_metric("latency"))) + print("design's power: " + str(sim_dp.dp_stats.get_system_complex_metric("power"))) + print("design's area: " + str(sim_dp.dp_stats.get_system_complex_metric("area"))) + print("design's sub area: " + str(sim_dp.dp_stats.get_system_complex_area_stacked_dram())) + + + return (ex_dp, sim_dp), total_trans_cnt + + + def protected_gen_neigh_and_eval(self, des_tup): + ctr = 0 + while True and ctr <100: + ctr +=1 + try: + des_tup_new, possible_des_cnt = self.gen_neigh_and_eval(des_tup) + return des_tup_new, possible_des_cnt + break + except SystemExit: + print("caught an exit") + continue + except Exception as e: + print("caught an exception") + print("return too many exception or exits") + exit(0) + + # ------------------------------ + # Functionality: + # generate neighbours and evaluate them. + # neighbours are generated based on the depth and breath count determined in the config file. + # Depth means vertical, i.e., daisy chaining of the moves). Breadth means horizontal exploration. + # Variables: + # des_tup: starting point design point tuple (design point, simulated design point) + # breadth: the breadth according to which to generate designs (used for breadth wise search) + # depth: the depth according to which to generate designs (used for look ahead) + # ------------------------------ + def gen_some_neighs_and_eval(self, des_tup, breath_length, depth_length, des_tup_list): + # base case + if depth_length == 0: + return [des_tup] + #des_tup_list = [] + # iterate on breath + for i in range(0, breath_length): + self.SA_current_mini_breadth = 0 + if not(breath_length == 1): + self.SA_current_breadth += 1 + self.SA_current_depth = -1 + print("--------breadth--------") + # iterate on depth (generate one neighbour and evaluate it) + self.SA_current_depth += 1 + #des_tup_new, possible_des_cnt = self.gen_neigh_and_eval(des_tup) + des_tup_new, possible_des_cnt = self.protected_gen_neigh_and_eval(des_tup) + + #self.total_iteration_ctr += 1 + + # collect the generate design in a list and run sanity check on it + des_tup_list.append(des_tup_new) + + # do more coverage if needed + """ + for i in range(0, max(possible_des_cnt,1)-1): + self.SA_current_mini_breadth += 1 + des_tup_new_breadth, _ = self.gen_neigh_and_eval(des_tup) + des_tup_list.append(des_tup_new_breadth) + """ + # just a quick optimization, since there is not need + # to go deeper if we encounter identity. + # This is because we will keep repeating the identity at the point + if des_tup_new[1].move_applied.get_transformation_name() == "identity": + break + + self.gen_some_neighs_and_eval(des_tup_new, 1, depth_length-1, des_tup_list) + #des_tup_list.extend(self.gen_some_neighs_and_eval(des_tup_new, 1, depth_length-1)) + + # visualization and sanity checks + if config.VIS_MOVE_TRAIL: + if (self.population_generation_cnt % config.vis_reg_ctr_threshold) == 0: + best_design_sim_cpy = copy.deepcopy(self.so_far_best_sim_dp) + self.des_trail_list.append((best_design_sim_cpy, des_tup_list[-1][1])) + self.last_des_trail = (best_design_sim_cpy, des_tup_list[-1][1]) + #self.des_trail_list.append((cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp, -1)),cPickle.loads(cPickle.dumps(des_tup_list[-1][1], -1)))) + #self.last_des_trail = (cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp, -1)),cPickle.loads(cPickle.dumps(des_tup_list[-1][1], -1))) + + + #self.vis_move_ctr += 1 + if config.DEBUG_SANITY: des_tup[0].sanity_check() + #return des_tup_list + + # simple simulated annealing + def simple_SA(self): + # define the result dictionary + this_itr_ex_sim_dp_dict:Dict[ExDesignPoint: SimDesignPoint] = {} + this_itr_ex_sim_dp_dict[self.so_far_best_ex_dp] = self.so_far_best_sim_dp # init the res dict + + # navigate the space using depth and breath parameters + strt = time.time() + print("------------------------ itr:" + str(self.population_generation_cnt) + " ---------------------") + self.SA_current_breadth = -1 + self.SA_current_depth = -1 + + # generate some neighbouring design points and evaluate them + des_tup_list =[] + #config.SA_depth = 3*len(self.so_far_best_ex_dp.get_hardware_graph().get_blocks_by_type("mem"))+ len(self.so_far_best_ex_dp.get_hardware_graph().get_blocks_by_type("ic")) + self.gen_some_neighs_and_eval((self.so_far_best_ex_dp, self.so_far_best_sim_dp), config.SA_breadth, config.SA_depth, des_tup_list) + exploration_and_simulation_approximate_time_per_iteration = (time.time() - strt)/max(len(des_tup_list), 1) + #print("sim time + neighbour generation per design point " + str((time.time() - strt)/max(len(des_tup_list), 1))) + + # convert (outputed) list to dictionary of (ex:sim) specified above. + # Also, run sanity check on the design, making sure everything is alright + for ex_dp, sim_dp in des_tup_list: + sim_dp.add_exploration_and_simulation_approximate_time(exploration_and_simulation_approximate_time_per_iteration) + this_itr_ex_sim_dp_dict[ex_dp] = sim_dp + if config.DEBUG_SANITY: + ex_dp.sanity_check() + return this_itr_ex_sim_dp_dict + + + def convert_tuple_list_to_parsable_csv(self, list_): + result = "" + for k, v in list_: + result +=str(k) + "=" + str(v) + "___" + return result + + def convert_dictionary_to_parsable_csv_with_underline(self, dict_): + result = "" + for k, v in dict_.items(): + phase_value_dict = list(v.values())[0] + value = list(phase_value_dict.values())[0] + result +=str(k) + "=" + str(value) + "___" + return result + + def convert_dictionary_to_parsable_csv_with_semi_column(self, dict_): + result = "" + for k, v in dict_.items(): + result +=str(k) + "=" + str(v) + ";" + return result + + # ------------------------------ + # Functionality: + # Explore the initial design. Basically just simulated the initial design + # Variables + # it uses the config parameters that are used to instantiate the object. + # ------------------------------ + def explore_one_design(self): + self.so_far_best_ex_dp = self.init_ex_dp + self.init_sim_dp = self.so_far_best_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + this_itr_ex_sim_dp_dict = {} + this_itr_ex_sim_dp_dict[self.so_far_best_ex_dp] = self.so_far_best_sim_dp + #self.init_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + + # collect statistics about the design + self.log_data(this_itr_ex_sim_dp_dict) + self.collect_stats(this_itr_ex_sim_dp_dict) + + # visualize/checkpoint/PA generation + vis_hardware.vis_hardware(self.so_far_best_sim_dp.get_dp_rep()) + if config.RUN_VERIFICATION_PER_GEN or config.RUN_VERIFICATION_PER_IMPROVMENT or config.RUN_VERIFICATION_PER_NEW_CONFIG: + self.gen_verification_data(self.so_far_best_sim_dp, self.so_far_best_ex_dp) + + def get_log_data(self): + return self.log_data_list + + def write_data_log(self, log_data, reason_to_terminate, case_study, result_dir_specific, unique_number, file_name): + output_file_all = os.path.join(result_dir_specific, file_name + "_all_reults.csv") + csv_columns = list(log_data[0].keys()) + # minimal output + with open(output_file_all, 'w') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=csv_columns) + writer.writeheader() + for data in log_data: + writer.writerow(data) + + # ------------------------------ + # Functionality: + # log the data for plotting and such + # ------------------------------ + def log_data(self, this_itr_ex_sim_dp_dict): + ctr = len(self.log_data_list) + for sim_dp in this_itr_ex_sim_dp_dict.values(): + sim_dp.add_exploration_and_simulation_approximate_time(self.neighbour_selection_time/len(list(this_itr_ex_sim_dp_dict.keys()))) + ma = sim_dp.get_move_applied() # move applied + if not ma == None: + sorted_metrics = self.convert_tuple_list_to_parsable_csv( + [(el, val) for el, val in ma.sorted_metrics.items()]) + metric = ma.get_metric() + transformation_name = ma.get_transformation_name() + task_name = ma.get_kernel_ref().get_task_name() + block_type = ma.get_block_ref().type + dir = ma.get_dir() + generation_time = ma.get_generation_time() + sorted_blocks = self.convert_tuple_list_to_parsable_csv( + [(el.get_generic_instance_name(), val) for el, val in ma.sorted_blocks]) + sorted_kernels = self.convert_tuple_list_to_parsable_csv( + [(el.get_task_name(), val) for el, val in ma.sorted_kernels.items()]) + blk_instance_name = ma.get_block_ref().get_generic_instance_name() + blk_type = ma.get_block_ref().type + comm_comp = (ma.get_system_improvement_log())["comm_comp"] + high_level_optimization = (ma.get_system_improvement_log())["high_level_optimization"] + exact_optimization = (ma.get_system_improvement_log())["exact_optimization"] + architectural_principle = (ma.get_system_improvement_log())["architectural_principle"] + block_selection_time = ma.get_logs("block_selection_time") + kernel_selection_time = ma.get_logs("kernel_selection_time") + transformation_selection_time = ma.get_logs("transformation_selection_time") + pickling_time = ma.get_logs("pickling_time") + metric_selection_time = ma.get_logs("metric_selection_time") + dir_selection_time = ma.get_logs("dir_selection_time") + move_validity = ma.is_valid() + ref_des_dist_to_goal_all = ma.get_logs("ref_des_dist_to_goal_all") + ref_des_dist_to_goal_non_cost = ma.get_logs("ref_des_dist_to_goal_non_cost") + #neighbouring_design_space_size = self.convert_dictionary_to_parsable_csv_with_semi_column(ma.get_design_space_size()) + neighbouring_design_space_size = sim_dp.get_neighbouring_design_space_size() + workload = ma.get_logs("workload") + else: # happens at the very fist iteration + pickling_time = 0 + sorted_metrics = "" + metric = "" + transformation_name = "" + task_name = "" + block_type = "" + dir = "" + generation_time = '' + sorted_blocks = '' + sorted_kernels = {} + blk_instance_name = '' + blk_type = '' + comm_comp = "" + high_level_optimization = "" + exact_optimization = "" + architectural_principle = "" + block_selection_time = "" + kernel_selection_time = "" + metric_selection_time = "" + dir_selection_time = "" + transformation_selection_time = "" + move_validity = "" + ref_des_dist_to_goal_all = "" + ref_des_dist_to_goal_non_cost = "" + neighbouring_design_space_size = "" + workload = "" + + + sub_block_area_break_down = self.convert_dictionary_to_parsable_csv_with_underline(sim_dp.dp_stats.SOC_area_subtype_dict) + block_area_break_down = self.convert_dictionary_to_parsable_csv_with_underline(sim_dp.dp_stats.SOC_area_dict) + routing_complexity = sim_dp.dp_rep.get_hardware_graph().get_routing_complexity() + area_non_dram = sim_dp.dp_stats.get_system_complex_area_stacked_dram()["non_dram"] + area_dram = sim_dp.dp_stats.get_system_complex_area_stacked_dram()["dram"] + simple_topology = sim_dp.dp_rep.get_hardware_graph().get_simplified_topology_code() + channel_cnt = sim_dp.dp_rep.get_hardware_graph().get_number_of_channels() + blk_cnt = sum([int(el) for el in simple_topology.split("_")]) + bus_cnt = [int(el) for el in simple_topology.split("_")][0] + mem_cnt = [int(el) for el in simple_topology.split("_")][1] + pe_cnt = [int(el) for el in simple_topology.split("_")][2] + task_cnt = len(list(sim_dp.dp_rep.krnl_phase_present.keys())) + #itr_depth_multiplied = sim_dp.dp_rep.get_iteration_number() * config.SA_depth + sim_dp.dp_rep.get_depth_number() + + self.total_iteration_cnt = ctr + data = { + "data_number": ctr, + "iteration cnt" : self.total_iteration_cnt, + "exploration_plus_simulation_time" : sim_dp.get_exploration_and_simulation_approximate_time(), + #"phase_calculation_time": sim_dp.get_phase_calculation_time(), + # "task_update_time": sim_dp.get_task_update_time(), + #"phase_scheduling_time": sim_dp.get_phase_scheduling_time(), + "observed population number" : sim_dp.dp_rep.get_population_observed_number(), + "SA_total_depth": str(config.SA_depth), + "transformation_selection_mode": str(config.transformation_selection_mode), + "workload": workload, + "heuristic_type": config.heuristic_type, + "population generation cnt": sim_dp.dp_rep.get_population_generation_cnt(), + "simulation time" : sim_dp.dp_rep.get_simulation_time(), + "transformation generation time" : generation_time, + "metric selection time" :metric_selection_time, + "dir selection time" :dir_selection_time, + "kernel selection time" :kernel_selection_time, + "block selection time" : block_selection_time, + "transformation selection time" : transformation_selection_time, + "design duplication time": pickling_time, + "neighbour selection time": self.neighbour_selection_time, + "dist_to_goal_all" : sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power", "cost"], + mode="eliminate"), + "dist_to_goal_non_cost" : sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power"], + mode="eliminate"), + "ref_des_dist_to_goal_all" : ref_des_dist_to_goal_all, + "ref_des_dist_to_goal_non_cost" : ref_des_dist_to_goal_non_cost, + "best_des_so_far_dist_to_goal_non_cost": self.so_far_best_sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power"], + mode="eliminate"), + "best_des_so_far_dist_to_goal_all": self.so_far_best_sim_dp.dp_stats.dist_to_goal(metrics_to_look_into=["area", "latency", "power"], + mode="eliminate"), + "best_des_so_far_area_non_dram": self.so_far_best_sim_dp.dp_stats.get_system_complex_area_stacked_dram()["non_dram"], + "best_des_so_far_area_dram": self.so_far_best_sim_dp.dp_stats.get_system_complex_area_stacked_dram()["dram"], + #"area_breakdown_subtype":self.convert_dictionary_to_parsable_csv_with_semi_column(sim_dp.dp_stats.SOC_area_subtype_dict.keys()), + #"best_des_so_far_area_breakdown_subtype":self.so_far_best_sim_dp.dp_stats.convert_dictionary_to_parsable_csv_with_semi_column(sim_dp.dp_stats.SOC_area_subtype_dict.keys()), + "system block count" : blk_cnt, + "system PE count" : pe_cnt, + "system bus count" : bus_cnt, + "system memory count" : mem_cnt, + "routing complexity" : routing_complexity, + "workload_set" : '_'.join(sim_dp.database.db_input.workload_tasks.keys()), + "block_impact_sorted" : sorted_blocks, + "kernel_impact_sorted" : sorted_kernels, + "metric_impact_sorted" : sorted_metrics, + "transformation_metric" : metric, + "move validity" : move_validity, + "move name" : transformation_name, + "transformation_kernel" : task_name, + "transformation_block_name" : blk_instance_name, + "transformation_block_type" : blk_type, + "transformation_dir" : dir, + "comm_comp" : comm_comp, + "high level optimization name" : high_level_optimization, + "exact optimization name": exact_optimization, + "architectural principle" : architectural_principle, + "neighbouring design space size" : neighbouring_design_space_size, + "block_area_break_down":block_area_break_down, + "sub_block_area_break_down":sub_block_area_break_down, + "task_cnt": task_cnt, + "channel_cnt":channel_cnt, + "area_dram":area_dram, + "area_non_dram":area_non_dram + } + + for metric in config.all_metrics: + # convert dictionary to a parsable data + data_ = sim_dp.dp_stats.get_system_complex_metric(metric) + if isinstance(data_, dict): + data__ =self.convert_dictionary_to_parsable_csv_with_semi_column(data_) + else: + data__ = data_ + data[metric] = data__ + + if metric in sim_dp.database.db_input.get_budget_dict("glass").keys(): + # convert dictionary to a parsable rsult + data_ = sim_dp.database.db_input.get_budget_dict("glass")[metric] + if isinstance(data_, dict): + data__ = self.convert_dictionary_to_parsable_csv_with_semi_column(data_) + else: + data__ = data_ + data[metric +"_budget"] = data__ + + for metric in config.all_metrics: + # convert dictionary to a parsable data + data_ = self.so_far_best_sim_dp.dp_stats.get_system_complex_metric(metric) + if isinstance(data_, dict): + data__ = self.convert_dictionary_to_parsable_csv_with_semi_column(data_) + else: + data__ = data_ + data["best_des_so_far_"+metric] = data__ + + ctr +=1 + self.log_data_list.append(data) + + + def sel_next_dp_for_moos(self, ex_sim_dp_dict, best_sim_dp_so_far, best_ex_dp_so_far, cur_temp): + # convert to stats + sim_dp_list = list(ex_sim_dp_dict.values()) + sim_dp_stat_list = [sim_dp.dp_stats for sim_dp in sim_dp_list] + sim_stat_ex_dp_dict = {} + for k, v in ex_sim_dp_dict.items(): + sim_stat_ex_dp_dict[v.dp_stats] = k + + + # find the ones that fit the expanded budget (note that budget radius shrinks) + selected_sim_dp, found_improved_solution = self.moos_greedy_design_selection(sim_stat_ex_dp_dict, sim_dp_stat_list, + best_ex_dp_so_far, best_sim_dp_so_far.dp_stats, + cur_temp) + + if not found_improved_solution: + selected_sim_dp = self.so_far_best_sim_dp + selected_ex_dp = self.so_far_best_ex_dp + else: + # extract the design + for key, val in ex_sim_dp_dict.items(): + key.sanity_check() + if val == selected_sim_dp: + selected_ex_dp = key + break + + # generate verification data + if found_improved_solution and config.RUN_VERIFICATION_PER_IMPROVMENT: + self.gen_verification_data(selected_sim_dp, selected_ex_dp) + return selected_ex_dp, selected_sim_dp, found_improved_solution + + + def greedy_for_moos(self, starting_ex_sim, moos_greedy_mode = "ctr"): + this_itr_ex_sim_dp_dict_all = {} + greedy_ctr_run = 0 + design_collected_ctr = 0 + + if moos_greedy_mode == 'ctr': + while greedy_ctr_run < config.MOOS_GREEDY_CTR_RUN and design_collected_ctr < config.DESIGN_COLLECTED_PER_GREEDY: + this_itr_ex_sim_dp_dict = self.simple_SA() # run simple simulated annealing + self.cur_best_ex_dp, self.cur_best_sim_dp, found_improvement = self.sel_next_dp_for_moos(this_itr_ex_sim_dp_dict, + self.so_far_best_sim_dp, self.so_far_best_ex_dp, 1) + + for ex, sim in this_itr_ex_sim_dp_dict.items(): + this_itr_ex_sim_dp_dict_all[ex] = sim + + self.so_far_best_sim_dp = self.cur_best_sim_dp + self.so_far_best_ex_dp = self.cur_best_ex_dp + self.found_any_improvement = self.found_any_improvement or found_improvement + design_collected_ctr = len(this_itr_ex_sim_dp_dict_all) + greedy_ctr_run +=1 + elif moos_greedy_mode == 'neighbour': + found_improvement = True + while found_improvement: + this_itr_ex_sim_dp_dict = self.simple_SA() # run simple simulated annealing + self.cur_best_ex_dp, self.cur_best_sim_dp, found_improvement = self.sel_next_dp_for_moos( + this_itr_ex_sim_dp_dict, + self.so_far_best_sim_dp, self.so_far_best_ex_dp, 1) + + for ex, sim in this_itr_ex_sim_dp_dict.items(): + this_itr_ex_sim_dp_dict_all[ex] = sim + + self.so_far_best_sim_dp = self.cur_best_sim_dp + self.so_far_best_ex_dp = self.cur_best_ex_dp + self.found_any_improvement = self.found_any_improvement or found_improvement + design_collected_ctr = len(this_itr_ex_sim_dp_dict_all) + greedy_ctr_run += 1 + elif moos_greedy_mode == "phv": + phv_improvement = True + hyper_volume_ref = [300, 2, 2] + local_pareto = {} + phv_so_far = 0 + while phv_improvement and greedy_ctr_run < config.MOOS_GREEDY_CTR_RUN: + # run hill climbing + this_itr_ex_sim_dp_dict = self.simple_SA() # run simple simulated annealing + # get best neighbour + self.cur_best_ex_dp, self.cur_best_sim_dp, found_improvement = self.sel_next_dp_for_moos( + this_itr_ex_sim_dp_dict, + self.so_far_best_sim_dp, self.so_far_best_ex_dp, 1) + + # find best neighbour + for ex, sim in this_itr_ex_sim_dp_dict.items(): + if ex == self.cur_best_ex_dp: + best_neighbour_ex = ex + best_neighbour_sim = sim + break + + for ex, sim in this_itr_ex_sim_dp_dict.items(): + this_itr_ex_sim_dp_dict_all[ex] = sim + + # update the pareto with new best neighbour + new_pareto = {} + for ex, sim in local_pareto.items(): + new_pareto[ex] = sim + new_pareto[best_neighbour_ex] = best_neighbour_sim + pareto_designs = self.get_pareto_designs(new_pareto) + pareto_with_best_neighbour = self.evaluate_pareto(new_pareto, hyper_volume_ref) + phv_improvement = pareto_with_best_neighbour > phv_so_far + + # if phv improved, add the neighbour to the local pareto + + if phv_improvement: + local_pareto = {} + for ex, sim in new_pareto.items(): + local_pareto[ex] = sim + phv_so_far = pareto_with_best_neighbour + + greedy_ctr_run +=1 + + #result = {self.cur_best_ex_dp: self.cur_best_sim_dp} + result = this_itr_ex_sim_dp_dict_all + return result + + + def get_pareto_designs(self, ex_sim_designs): + pareto_designs = {} + point_list = [] + # iterate through the designs and generate points ([latency, power, area] tuple or points) + for ex, sim in ex_sim_designs.items(): + point = [] + for metric_name in config.budgetted_metrics: + for type, id in sim.dp_stats.get_designs_SOCs(): + if metric_name == "latency": + metric_val = sum(list(sim.dp_stats.get_SOC_metric_value(metric_name, type, id).values())) + #metric_val = format(metric_val, ".10f") + else: + metric_val = sim.dp_stats.get_SOC_metric_value(metric_name, type, id) + #metric_val = format(metric_val, ".10f") + + + point.append(metric_val) + point_list.append(point) + + # find the pareto points + pareto_points = self.find_pareto_points(point_list) + remove_list = [] + # extract the designs according to the pareto points + for ex, sim in ex_sim_designs.items(): + point = [] + for metric_name in config.budgetted_metrics: + for type, id in sim.dp_stats.get_designs_SOCs(): + if metric_name == "latency": + metric_val = sum(list(sim.dp_stats.get_SOC_metric_value(metric_name, type, id).values())) + else: + metric_val = sim.dp_stats.get_SOC_metric_value(metric_name, type, id) + + #metric_val = format(metric_val, ".10f") + point.append(metric_val) + if point in pareto_points: + pareto_points.remove(point) # no double counting + pareto_designs[ex] = sim + else: + remove_list.append(ex) + + for el in remove_list: + del ex_sim_designs[el] + + if pareto_designs == {}: + print("hmm there shoujld be a point in the pareto design") + return pareto_designs + + def find_pareto_points(self, points): + def is_pareto_efficient_dumb(costs): + is_efficient = np.ones(costs.shape[0], dtype=bool) + for i, c in enumerate(costs): + is_efficient[i] = np.all(np.any(costs[:i] > c, axis=1)) and np.all(np.any(costs[i + 1:] > c, axis=1)) + return is_efficient + + # removing the duplicates (other wise we get wrong results for pareto front) + points.sort() + points = list(k for k, _ in itertools.groupby(points)) + efficients = is_pareto_efficient_dumb(np.array(points)) + pareto_points_array = [points[idx] for idx, el in enumerate(efficients) if el] + + return pareto_points_array + + """ + pareto_points = [] + for el in pareto_points_array: + list_ = [] + for el_ in el: + list.append(el) + pareto_points.append(list_) + + return pareto_points + """ + + def evaluate_pareto(self, pareto_ex_sim, ref): + point_list = [] + for ex, sim in pareto_ex_sim.items(): + point = [] + for metric_name in config.budgetted_metrics: + for type, id in sim.get_designs_SOCs(): + if metric_name == "latency": + metric_val = sum(list(sim.dp_stats.get_SOC_metric_value(metric_name, type, id).values())) + else: + metric_val = sim.dp_stats.get_SOC_metric_value(metric_name, type, id) + + #metric_val = format(metric_val, ".10f") + point.append(metric_val) + point_list.append(point) + + + hv = hypervolume(point_list) + hv_value = hv.compute(ref) + + return hv_value + + # ------------------------------ + # Functionality: + # Explore the design space. + # Variables + # it uses the config parameters that are used to instantiate the object. + # ------------------------------ + def explore_ds_with_moos(self): + #gc.DEBUG_SAVEALL = True + self.so_far_best_ex_dp = self.init_ex_dp + self.so_far_best_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + self.init_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + + # visualize/checkpoint/PA generation + vis_hardware.vis_hardware(self.so_far_best_sim_dp.get_dp_rep()) + if config.RUN_VERIFICATION_PER_GEN or config.RUN_VERIFICATION_PER_IMPROVMENT or config.RUN_VERIFICATION_PER_NEW_CONFIG: + self.gen_verification_data(self.so_far_best_sim_dp, self.so_far_best_ex_dp) + + + #num_of_workloads = len(self.so_far_best_sim_dp.dp_stats.database.get_workloads_last_task().values()) + # initializing the tree + self.pareto_global = {} + self.pareto_global [self.so_far_best_ex_dp] = self.so_far_best_sim_dp + hyper_volume_ref = [300,2,2] + + pareto_global_child_evaluation = self.evaluate_pareto(self.pareto_global, hyper_volume_ref) + root_node = self.moos_tree.get_root() + root_node.update_evaluation(pareto_global_child_evaluation) + best_leaf = self.moos_tree.get_root() + + des_per_iteration = [0] + start = True + cur_temp = config.annealing_max_temp + + pareto_global_init = {} + pareto_global_child = {} + should_terminate = False + reason_to_termiante = "" + ctr = 0 + while not should_terminate: + # get pareto designs + self.pareto_global = self.get_pareto_designs(self.pareto_global) + # expand the tree + best_leaf = self.moos_tree.find_node_to_expand() + best_leaf_evaluation = best_leaf.get_evaluation() + expanded = self.moos_tree.expand(best_leaf) + ctr +=1 + # if expansion failed, terminate + # usually happens when the intervals are too small + if not expanded: + should_terminate, reason_to_terminate = True, "no_interval_for_moos" + ctr +=1 + # populate the pareto (local pareto) + pareto_global_init.clear() + for ex, sim in self.pareto_global.items(): + pareto_global_init[ex] = sim + + # iterate through the children and run greedy heuristic + for name, child in best_leaf.get_children().items(): + if should_terminate: + continue + + if name == "center": + pareto_global_child_evaluation = best_leaf_evaluation + child.update_evaluation(pareto_global_child_evaluation) + continue + + # populate the pareto front with pareto global + pareto_global_child.clear() + for ex, sim in pareto_global_init.items(): + pareto_global_child[ex] = sim + lambdas = child.get_lambdas() + self.cur_best_ex_dp, self.cur_best_sim_dp = self.sel_start_dp_moos(pareto_global_child, + self.so_far_best_sim_dp, self.so_far_best_ex_dp, lambdas) + + # use the follow as a the starting point for greedy heuristic + self.so_far_best_ex_dp = self.cur_best_ex_dp + self.so_far_best_sim_dp = self.cur_best_sim_dp + #this_itr_ex_sim_dp_dict = {self.so_far_best_ex_dp: self.so_far_best_sim_dp} + this_itr_ex_sim_dp_dict = self.greedy_for_moos(self.so_far_best_ex_dp, config.moos_greedy_mode) # run simple simulated annealing + + + self.total_iteration_ctr += len(list(this_itr_ex_sim_dp_dict.keys())) + + """ + # collect profiling information about moves and designs generated + if config.VIS_MOVE_TRAIL and (self.population_generation_cnt% config.vis_reg_ctr_threshold) == 0 and len(self.des_trail_list) > 0: + plot.des_trail_plot(self.des_trail_list, self.move_profile, des_per_iteration) + plot.move_profile_plot(self.move_profile) + """ + + # get new pareto design and merge, and evaluate (update the tree) + self.log_data(this_itr_ex_sim_dp_dict) + self.collect_stats(this_itr_ex_sim_dp_dict) + pareto_designs = self.get_pareto_designs(this_itr_ex_sim_dp_dict) + pareto_global_child.update(pareto_designs) + pareto_global_child = self.get_pareto_designs(pareto_global_child) + pareto_global_child_evaluation = self.evaluate_pareto(pareto_global_child, hyper_volume_ref) + print("pareto evluation" + str(pareto_global_child_evaluation)) + child.update_evaluation(pareto_global_child_evaluation) + + # update pareto global + for ex, sim in pareto_global_child.items(): + self.pareto_global[ex] = sim + #self.pareto_global.update(pareto_global_child) + + """ + if config.VIS_GR_PER_ITR and (self.population_generation_cnt% config.vis_reg_ctr_threshold) == 0: + vis_hardware.vis_hardware(self.cur_best_sim_dp.get_dp_rep()) + """ + # collect statistics about the design + gc.collect() + + # update and check for termination + print("memory usage ===================================== " +str(psutil.virtual_memory())) + # check terminattion status + should_terminate, reason_to_terminate = self.update_ctrs() + mem = psutil.virtual_memory() + mem_used = int(mem.percent) + if mem_used > config.out_of_memory_percentage: + should_terminate, reason_to_terminate = True, "out_of_memory" + + if should_terminate: + # get the best design + self.cur_best_ex_dp, self.cur_best_sim_dp = self.sel_next_dp(self.pareto_global, + self.so_far_best_sim_dp, + self.so_far_best_ex_dp, config.annealing_max_temp) + self.so_far_best_ex_dp = self.cur_best_ex_dp + self.so_far_best_sim_dp = self.cur_best_sim_dp + + print("reason to terminate is:" + reason_to_terminate) + vis_hardware.vis_hardware(self.cur_best_sim_dp.get_dp_rep()) + if not (self.last_des_trail == None): + if self.last_des_trail == None: + self.last_des_trail = ( + copy.deepcopy(self.so_far_best_sim_dp), copy.deepcopy(self.so_far_best_sim_dp)) + # self.last_des_trail = (cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp, -1)),cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp))) + else: + self.des_trail_list.append(self.last_des_trail) + if not (self.last_move == None): + self.move_profile.append(self.last_move) + + if config.VIS_MOVE_TRAIL: + plot.des_trail_plot(self.des_trail_list, self.move_profile, des_per_iteration) + plot.move_profile_plot(self.move_profile) + self.reason_to_terminate = reason_to_terminate + + return + + print(" >>>>> des" + " latency:" + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("latency"))) + + """ + stat_result = self.so_far_best_sim_dp.dp_stats + if stat_result.fits_budget(1): + should_terminate = True + reason_to_terminate = "met the budget" + elif self.population_generation_cnt > self.TOTAL_RUN_THRESHOLD: + should_terminate = True + reason_to_terminate = "exploration (total itr_ctr) iteration threshold reached" + + """ + + + def explore_simple_greedy_one_sample(self, orig_ex_dp, mode="random"): + orig_sim_dp = self.eval_design(self.init_ex_dp, self.database) + des_tup = [orig_ex_dp, orig_sim_dp] + des_tup_new, possible_des_cnt = self.gen_neigh_and_eval(des_tup) + + # ------------------------------ + # Functionality: + # Explore the design space. + # Variables + # it uses the config parameters that are used to instantiate the object. + # ------------------------------ + def explore_ds(self): + self.so_far_best_ex_dp = self.init_ex_dp + self.so_far_best_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + self.init_sim_dp = self.eval_design(self.so_far_best_ex_dp, self.database) + + # visualize/checkpoint/PA generation + vis_hardware.vis_hardware(self.so_far_best_sim_dp.get_dp_rep()) + if config.RUN_VERIFICATION_PER_GEN or config.RUN_VERIFICATION_PER_IMPROVMENT or config.RUN_VERIFICATION_PER_NEW_CONFIG: + self.gen_verification_data(self.so_far_best_sim_dp, self.so_far_best_ex_dp) + + des_per_iteration = [0] + start = True + cur_temp = config.annealing_max_temp + + while True: + this_itr_ex_sim_dp_dict = self.simple_SA() # run simple simulated annealing + self.total_iteration_ctr += len(list(this_itr_ex_sim_dp_dict.keys())) + + # collect profiling information about moves and designs generated + if config.VIS_MOVE_TRAIL and (self.population_generation_cnt% config.vis_reg_ctr_threshold) == 0 and len(self.des_trail_list) > 0: + plot.des_trail_plot(self.des_trail_list, self.move_profile, des_per_iteration) + plot.move_profile_plot(self.move_profile) + + # select the next best design + t1 = time.time() + self.cur_best_ex_dp, self.cur_best_sim_dp = self.sel_next_dp(this_itr_ex_sim_dp_dict, + self.so_far_best_sim_dp, self.so_far_best_ex_dp, cur_temp) + t2 = time.time() + self.neighbour_selection_time = t2-t1 + self.log_data(this_itr_ex_sim_dp_dict) + print("-------:):):):):)----------") + print("Best design's latency: " + str(self.cur_best_sim_dp.dp_stats.get_system_complex_metric("latency"))) + print("Best design's power: " + str(self.cur_best_sim_dp.dp_stats.get_system_complex_metric("power"))) + print("Best design's sub area: " + str(self.cur_best_sim_dp.dp_stats.get_system_complex_area_stacked_dram())) + + if not self.cur_best_sim_dp.move_applied == None: + self.cur_best_sim_dp.move_applied.print_info() + + if config.VIS_GR_PER_ITR and (self.population_generation_cnt% config.vis_reg_ctr_threshold) == 0: + vis_hardware.vis_hardware(self.cur_best_sim_dp.get_dp_rep()) + + # collect statistics about the design + self.collect_stats(this_itr_ex_sim_dp_dict) + + # determine if the design has met the budget, if so, terminate + mem = psutil.virtual_memory() + mem_used = int(mem.percent) + print("memory usage ===================================== " + str(psutil.virtual_memory())) + if mem_used > config.out_of_memory_percentage: + should_terminate, reason_to_terminate = True, "out_of_memory" + else: + should_terminate, reason_to_terminate = self.update_ctrs() + + if should_terminate: + print("reason to terminate is:" + reason_to_terminate) + vis_hardware.vis_hardware(self.cur_best_sim_dp.get_dp_rep()) + if not (self.last_des_trail == None): + if self.last_des_trail == None: + self.last_des_trail = (copy.deepcopy(self.so_far_best_sim_dp), copy.deepcopy(self.so_far_best_sim_dp)) + #self.last_des_trail = (cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp, -1)),cPickle.loads(cPickle.dumps(self.so_far_best_sim_dp))) + else: + self.des_trail_list.append(self.last_des_trail) + if not (self.last_move == None): + self.move_profile.append(self.last_move) + + if config.VIS_MOVE_TRAIL: + plot.des_trail_plot(self.des_trail_list, self.move_profile, des_per_iteration) + plot.move_profile_plot(self.move_profile) + self.reason_to_terminate = reason_to_terminate + return + cur_temp -= config.annealing_temp_dec + self.vis_move_trail_ctr += 1 + + # ------------------------------ + # Functionality: + # generating plots for data analysis + # ----------------------------- + def plot_data(self): + iterations = [iter*config.num_neighs_to_try for iter in self.design_itr] + if config.DATA_DELIVEYRY == "obfuscate": + plot.scatter_plot(iterations, [area/self.area_explored[0] for area in self.area_explored], ("iteration", "area"), self.database) + plot.scatter_plot(iterations, [power/self.power_explored[0] for power in self.power_explored], ("iteration", "power"), self.database) + latency_explored_normalized = [el/self.latency_explored[0] for el in self.latency_explored] + plot.scatter_plot(iterations, latency_explored_normalized, ("iteration", "latency"), self.database) + else: + plot.scatter_plot(iterations, [1000000*area/self.area_explored[0] for area in self.area_explored], ("iteration", "area"), self.database) + plot.scatter_plot(iterations, [1000*power/self.power_explored[0] for power in self.power_explored], ("iteration", "power"), self.database) + plot.scatter_plot(iterations, self.latency_explored/self.latency_explored[0], ("iteration", "latency"), self.database) + + # ------------------------------ + # Functionality: + # report the data collected in a humanly readable way. + # Variables: + # explorations_start_time: to exploration start time used to determine the end-to-end exploration time. + # ----------------------------- + def report(self, exploration_start_time): + exploration_end_time = time.time() + total_sim_time = exploration_end_time - exploration_start_time + print("*********************************") + print("------- Best Designs Metrics ----") + print("*********************************") + print("Best design's latency: " + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("latency")) + \ + ", ---- time budget:" + str(config.budgets_dict["glass"]["latency"])) + print("Best design's thermal power: " + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("power"))+ + ", ---- thermal power budget:" + str(config.budgets_dict["glass"]["power"])) + print("Best design's area: " + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("area")) + \ + ", ---- area budget:" + str(config.budgets_dict["glass"]["area"])) + print("Best design's energy: " + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("energy"))) + print("*********************************") + print("------- DSE performance --------") + print("*********************************") + print("Initial design's latency: " + str(self.init_sim_dp.dp_stats.get_system_complex_metric("latency"))) + print("Speed up: " + str(self.init_sim_dp.dp_stats.get_system_complex_metric("latency")/self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("latency"))) + print("Number of design points examined:" + str(self.population_generation_cnt*config.num_neighs_to_try)) + print("Time spent per design point:" + str(total_sim_time/(self.population_generation_cnt*config.num_neighs_to_try))) + print("The design meet the latency requirement: " + str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric("latency") < config.objective_budget)) + vis_hardware.vis_hardware(self.so_far_best_ex_dp) + if config.VIS_FINAL_RES: + vis_hardware.vis_hardware(self.so_far_best_ex_dp, config.hw_graphing_mode) + + # write the output + home_dir = os.getcwd() + FARSI_result_dir = config.FARSI_result_dir + FARSI_result_directory_path = os.path.join(home_dir, 'data_collection/data/', FARSI_result_dir) + output_file_verbose = os.path.join(FARSI_result_directory_path, config.FARSI_outputfile_prefix_verbose +".txt") + output_file_minimal = os.path.join(FARSI_result_directory_path, config.FARSI_outputfile_prefix_minimal +".csv") + + # minimal output + output_fh_minimal = open(output_file_minimal, "w") + for metric in config.all_metrics: + output_fh_minimal.write(metric+ ",") + output_fh_minimal.write("\n") + for metric in config.all_metrics: + output_fh_minimal.write(str(self.so_far_best_sim_dp.dp_stats.get_system_complex_metric(metric))+ ",") + output_fh_minimal.close() + + # verbose + output_fh_verbose = open(output_file_verbose, "w") + output_fh_verbose.write("iter_cnt" + ": ") + for el in range(0, len(self.power_explored)): + output_fh_verbose.write(str(el) +",") + + output_fh_verbose.write("\npower" + ": ") + for el in self.power_explored: + output_fh_verbose.write(str(el) +",") + + output_fh_verbose.write("\nlatency" + ": ") + for el in self.latency_explored: + output_fh_verbose.write(str(el) +",") + output_fh_verbose.write("\narea" + ": ") + for el in self.area_explored: + output_fh_verbose.write(str(el) +",") + + output_fh_verbose.close() + + # ------------------------------ + # Functionality: + # collect the profiling information for all the design generated by the explorer. For data analysis. + # Variables: + # ex_sim_dp_dict: example_design_simulated_design_dictionary. A dictionary containing the + # (example_design, simulated_design) tuple. + # ----------------------------- + def collect_stats(self, ex_sim_dp_dict): + for sim_dp in ex_sim_dp_dict.values(): + self.area_explored.append(sim_dp.dp_stats.get_system_complex_metric("area")) + self.power_explored.append(sim_dp.dp_stats.get_system_complex_metric("power")) + self.latency_explored.append(sim_dp.dp_stats.get_system_complex_metric("latency")) + self.design_itr.append(self.population_generation_cnt) + + # ------------------------------ + # Functionality: + # calculate the budget coefficients. This is used for simulated annealing purposes. + # Concretely, first we use relax budgets to allow wider exploration, and then + # incrementally tighten the budget to direct the explorer more toward the goal. + # ------------------------------ + def calc_budget_coeff(self): + self.budget_coeff = int(((self.TOTAL_RUN_THRESHOLD - self.population_generation_cnt)/self.coeff_slice_size) + 1) + + def reset_ctrs(self): + should_terminate = False + reason_to_terminate = "" + self.fitted_budget_ctr = 0 + self.krnels_not_to_consider = [] + self.des_stag_ctr = 0 + self.krnel_stagnation_ctr = 0 + self.krnel_rnk_to_consider = 0 + self.cleanup_ctr = 0 + self.des_stag_ctr = 0 + self.recently_seen_design_ctr = 0 + self.counters.reset() + self.moos_tree = moosTreeModel(config.budgetted_metrics) # only used for moos heuristic + self.found_any_improvement = False + + + + # ------------------------------ + # Functionality: + # Update the counters to determine the exploration (navigation heuristic) control path to follow. + # ------------------------------ + def update_ctrs(self): + should_terminate = False + reason_to_terminate = "" + + self.so_far_best_ex_dp = self.cur_best_ex_dp + self.so_far_best_sim_dp = self.cur_best_sim_dp + + self.population_generation_cnt += 1 + stat_result = self.so_far_best_sim_dp.dp_stats + + tasks_not_meeting_budget = [el.get_task_name() for el in self.filter_in_kernels_meeting_budget("", self.so_far_best_sim_dp)] + tsks_left_to_optimize = list(set(tasks_not_meeting_budget) - set(self.krnels_not_to_consider)) + + if stat_result.fits_budget(1) : + config.VIS_GR_PER_GEN = True # visualize the graph per design point generation + config.VIS_SIM_PER_GEN = True # if true, we visualize the simulation progression + self.fitted_budget_ctr +=1 + if (self.fitted_budget_ctr > config.fitted_budget_ctr_threshold): + reason_to_terminate = "met the budget" + should_terminate = True + elif self.des_stag_ctr > self.DES_STAG_THRESHOLD: + reason_to_terminate = "des_stag_ctr exceeded" + should_terminate = True + elif len(self.krnels_not_to_consider) >= (len(self.so_far_best_sim_dp.get_kernels()) - len(self.so_far_best_sim_dp.get_dummy_tasks())): + if stat_result.fits_budget(1): + reason_to_terminate = "met the budget" + else: + reason_to_terminate = "all kernels already targeted without improvement" + should_terminate = True + elif len(tsks_left_to_optimize) == 0: + if stat_result.fits_budget(1): + reason_to_terminate = "met the budget" + else: + reason_to_terminate = "all kernels already targeted without improvement" + should_terminate = True + elif self.total_iteration_ctr > self.TOTAL_RUN_THRESHOLD: + if stat_result.fits_budget(1): + reason_to_terminate = "met the budget" + else: + reason_to_terminate = "exploration (total itr_ctr) iteration threshold reached" + should_terminate = True + + self.counters.update(self.krnel_rnk_to_consider, self.krnel_stagnation_ctr, self.fitted_budget_ctr, self.des_stag_ctr, + self.krnels_not_to_consider, self.population_generation_cnt, self.found_any_improvement, self.total_iteration_ctr) + + print(">>>>> total iteration count is: " + str(self.total_iteration_ctr)) + return should_terminate, reason_to_terminate + + +class moosTreeNode: + def __init__(self, k_intervals): + self.k_ins = k_intervals + self.children = {} + self.evaluation = "None" + + def update_evaluation(self, evaluation): + self.evaluation = evaluation + + def get_evaluation(self): + if self.evaluation == "None": + print("must populate the evluation first") + exit(0) + return self.evaluation + + def get_k_ins(self): + return self.k_ins + + def get_interval(self, metric): + return self.get_k_ins()[metric] + + + def get_interval_length(self, metric): + assert(metric in self.k_ins.keys()) + interval = self.k_ins[metric] + length = interval[1] - interval[0] + if (length <=0): + print("length" + str(length)) + print("interval is:") + print(str(self.k_ins[metric])) + assert(length > 0) + return length + + def longest_dimension_name(self): + key = list(self.get_k_ins().keys())[0] + max_dimension = self.get_interval_length(key) + max_key = key + #print("max_dimentions" + str(max_dimension)) + + for k,v in self.get_k_ins().items(): + if self.get_interval_length(k) > max_dimension: + max_dimension = self.get_interval_length(k) + max_key = k + + return max_key + + def update_interval(self, key, interval): + self.k_ins[key] = interval + + def add_children(self, left_children_, center_children_, right_children_): + self.children["left"]= left_children_ + self.children["center"] = center_children_ + self.children["right"] = right_children_ + + def get_children_with_position(self, position): + return self.children[position] + + def get_children(self): + return self.children + + def get_lambdas(self): + lambdas = {} + for metric_name, val in self.k_ins.items(): + lambdas[metric_name] = (val[1] - val[0])/2 + return lambdas + + +class moosTreeModel: + def __init__(self, metric_names): + max = Decimal(1000000000) + min = Decimal(0) + node_val = [] + k_ins = {} + for el in metric_names: + k_ins[el] = [min, max] + self.root = moosTreeNode(k_ins) + + def get_root(self): + return self.root + + def get_leaves_with_depth(self): + def get_leaves_helper(node, depth): + result = [] + if node.get_children() == {}: + result = [(node, depth)] + else: + for position, child in node.get_children().items(): + child_result = get_leaves_helper(child, depth+1) + result.extend(child_result) + return result + + leaves_depth = get_leaves_helper(self.root, 0) + return leaves_depth + + def expand(self, node): + # initializing the longest + longest_dimension_key = node.longest_dimension_name() + longest_dimension_interval = node.get_interval(longest_dimension_key) + longest_intr_min = min(longest_dimension_interval) + longest_intr_max = max(longest_dimension_interval) + longest_dimension_incr = Decimal(longest_intr_max - longest_intr_min)/3 + + child_center = moosTreeNode(copy.deepcopy(node.get_k_ins())) + child_left = moosTreeNode(copy.deepcopy(node.get_k_ins())) + child_right = moosTreeNode(copy.deepcopy(node.get_k_ins())) + + if (longest_intr_min - longest_intr_min + 1*longest_dimension_incr == 0): + print("this shouldn't happen. intervals should be the same") + print("intrval" + str(longest_dimension_interval)) + print("incr" + str(longest_dimension_incr)) + print("min" +str(longest_intr_min)) + print("max" + str(longest_intr_max)) + print("first upper" + str(longest_intr_min + 1*longest_dimension_incr)) + print("second upper"+ str(longest_intr_min + 2 * longest_dimension_incr)) + return False + + if (longest_intr_min + 1 * longest_dimension_incr - longest_intr_min + 2 * longest_dimension_incr == 0): + print("this shouldn't happen. intervals should be the same") + print(longest_dimension_interval) + print(longest_dimension_incr) + print(longest_intr_min) + print(longest_intr_min + 1*longest_dimension_incr) + print(longest_intr_min + 2 * longest_dimension_incr) + print(longest_intr_max) + return False + + if (longest_intr_min + 2 * longest_dimension_incr - longest_intr_max == 0): + print("this shouldn't happen. intervals should be the same") + print(longest_dimension_interval) + print(longest_dimension_incr) + print(longest_intr_min) + print(longest_intr_min + 1*longest_dimension_incr) + print(longest_intr_min + 2 * longest_dimension_incr) + print(longest_intr_max) + return False + + + child_left.update_interval(longest_dimension_key, [longest_intr_min, longest_intr_min + 1*longest_dimension_incr]) + child_center.update_interval(longest_dimension_key, [longest_intr_min + 1*longest_dimension_incr, longest_intr_min + 2*longest_dimension_incr]) + child_right.update_interval(longest_dimension_key, [longest_intr_min + 2*longest_dimension_incr, longest_intr_max]) + node.add_children(child_left, child_center, child_right) + return True + + def find_node_to_expand(self): + node_star_list = [] # selected node + leaves_with_depth = self.get_leaves_with_depth() + + # find max depth + max_depth = max([depth for leaf,depth in leaves_with_depth]) + + # split leaves to max and non max depth + leaves_with_max_depth = [leaf for leaf,depth in leaves_with_depth if depth == max_depth] + leaves_with_non_max_depth = [leaf for leaf,depth in leaves_with_depth if not depth == max_depth] + + # select node star + for max_leaf in leaves_with_max_depth: + leaf_is_better_list = [] + for non_max_leaf in leaves_with_non_max_depth: + leaf_is_better_list.append(max_leaf.get_evaluation() >= non_max_leaf.get_evaluation()) + if all(leaf_is_better_list): + node_star_list.append(max_leaf) + + + best_node = node_star_list[0] + for node in node_star_list: + if node.get_evaluation() > best_node.get_evaluation(): + best_node = node + """ + # for debugging. delete later + if (len(node_star_list) == 0): + for max_leaf in leaves_with_max_depth: + leaf_is_better_list = [] + for non_max_leaf in leaves_with_non_max_depth: + leaf_is_better_list.append(max_leaf.get_evaluation() >= non_max_leaf.get_evaluation()) + if all(leaf_is_better_list): + node_star_list.append(max_leaf) + + if not (len(node_star_list) == 1): + print("something went wrong") + """ + return best_node + + + + + + + + + + + diff --git a/Project_FARSI/DSE_utils/simple_minimal_change_ga.py b/Project_FARSI/DSE_utils/simple_minimal_change_ga.py new file mode 100644 index 00000000..307fcae9 --- /dev/null +++ b/Project_FARSI/DSE_utils/simple_minimal_change_ga.py @@ -0,0 +1,209 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. + +# This file is part of DEAP. +# +# DEAP is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# DEAP is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with DEAP. If not, see . + + +# example which maximizes the sum of a list of integers +# each of which can be 0 or 1 +import time +import random + +from deap import base +from deap import creator +from deap import tools +from datetime import datetime + +creator.create("FitnessMax", base.Fitness, weights=(1.0,)) +creator.create("Individual", list, fitness=creator.FitnessMax) + +toolbox = base.Toolbox() + +# Attribute generator +# define 'attr_bool' to be an attribute ('gene') +# which corresponds to integers sampled uniformly +# from the range [0,1] (i.e. 0 or 1 with equal +# probability) +bus_DB = range(0, 20) +memory_DB = range(20, 40) +ip_DB = range(40, 600) +block_to_choose_from = ["ip", "memory", "bus"] + +def individual_att_generator(): + global bus_DB + global memory_DB + global ip_DB + time.sleep(.00001) + random.seed(datetime.now().microsecond) + # for mating or mutation + #block_choice = random.choice(block_to_choose_from) + #alternate_block_val = random.choice(eval(block_choice+"_DB")) + + memory = random.choice(memory_DB) + ip = random.choice(ip_DB) + bus = random.choice(bus_DB) + return [ip, memory, bus] + +toolbox.register("attr_bool", individual_att_generator) + +# Structure initializers +# define 'individual' to be an individual +# consisting of 100 'attr_bool' elements ('genes') + +toolbox.register("individual", tools.initRepeat, creator.Individual, + toolbox.attr_bool, 1) + +# define the population to be a list of individuals +# should always be a list +toolbox.register("population", tools.initRepeat, list, toolbox.individual) + +# the goal ('fitness') function to be maximized +def eval_individual_dp(individual): + # call OSSIM individual is a design point + return [max(individual[0])] +#---------- +# Operator registration +#---------- +# register the goal / fitness function +toolbox.register("evaluate", eval_individual_dp) + +def mate_dp(individual1, individual2): + # register the crossover operator + # pick a random index and swap + time.sleep(.00001) + random.seed(datetime.now().microsecond + 10) + rand_idx = random.choice(list(range(0, len(individual1[0])))) + individual1_el = individual1[0][rand_idx] + individual1[0][rand_idx] = individual2[0][rand_idx] + individual2[0][rand_idx] = individual1_el + +toolbox.register("mate", mate_dp) + + +def mutate_dp(individual1, indpb): + # register the crossover operator + # pick a random index and swap + time.sleep(.00001) + random.seed(datetime.now().microsecond + 50) + rand_idx = random.choice(list(range(0, len(individual1[0])))) + individual1[0][rand_idx] = random.choice(list(range(40, 600))) + +# register a mutation operator with a probability to +# flip each attribute/gene of 0.05 +toolbox.register("mutate", mutate_dp, indpb=0.05) + +# operator for selecting individuals for breeding the next +# generation: each individual of the current generation +# is replaced by the 'fittest' (best) of three individuals +# drawn randomly from the current generation. +toolbox.register("select", tools.selTournament, tournsize=3) + +#---------- + +def main(): + time.sleep(.00001) + random.seed(datetime.now().microsecond + 70) + + # CXPB is the probability with which two individuals + # are crossed + # + # MUTPB is the probability for mutating an individual + CXPB, MUTPB = 0.5, 0.02 + max_num_gen = 10000 + max_pop_count = 20 + + # create an initial population of 300 individuals (where + # each individual is a list of integers) + pop = toolbox.population(n=max_pop_count) + + + print("Start of evolution") + + # Evaluate the entire population + fitnesses = list(map(toolbox.evaluate, pop)) + for ind, fit in zip(pop, fitnesses): + ind.fitness.values = fit + + print(" Evaluated %i individuals" % len(pop)) + + # Extracting all the fitnesses of + fits = [ind.fitness.values[0] for ind in pop] + + # Variable keeping track of the number of generations + num_gen = 0 + + # Begin the evolution + while num_gen < max_num_gen: + # A new generation + num_gen = num_gen + 1 + print("-- Generation %i --" % num_gen) + + # Select the next generation individuals + offspring = toolbox.select(pop, len(pop)) + # Clone the selected individuals + offspring = list(map(toolbox.clone, offspring)) + + # Apply crossover and mutation on the offspring + for child1, child2 in zip(offspring[::2], offspring[1::2]): + + # cross two individuals with probability CXPB + if random.random() < CXPB: + toolbox.mate(child1, child2) + + # fitness values of the children + # must be recalculated later + del child1.fitness.values + del child2.fitness.values + + for mutant in offspring: + + # mutate an individual with probability MUTPB + if random.random() < MUTPB: + toolbox.mutate(mutant) + del mutant.fitness.values + + # Evaluate the individuals with an invalid fitness + invalid_ind = [ind for ind in offspring if not ind.fitness.valid] + fitnesses = map(toolbox.evaluate, invalid_ind) + for ind, fit in zip(invalid_ind, fitnesses): + ind.fitness.values = fit + + print(" Evaluated %i individuals" % len(invalid_ind)) + + # The population is entirely replaced by the offspring + pop[:] = offspring + + # Gather all the fitnesses in one list and print the stats + fits = [ind.fitness.values[0] for ind in pop] + + length = len(pop) + mean = sum(fits) / length + sum2 = sum(x*x for x in fits) + std = abs(sum2 / length - mean**2)**0.5 + + print(" Min %s" % min(fits)) + print(" Max %s" % max(fits)) + print(" Avg %s" % mean) + print(" Std %s" % std) + + print("-- End of (successful) evolution --") + + best_ind = tools.selBest(pop, 1)[0] + print("Best individual is %s, %s" % (best_ind, best_ind.fitness.values)) + +if __name__ == "__main__": + main() diff --git a/Project_FARSI/EXTERNAL_CONTINUOUS_INTEGRATION_RUNS.md b/Project_FARSI/EXTERNAL_CONTINUOUS_INTEGRATION_RUNS.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/GHANGELOG.md b/Project_FARSI/GHANGELOG.md new file mode 100644 index 00000000..658c84cf --- /dev/null +++ b/Project_FARSI/GHANGELOG.md @@ -0,0 +1 @@ +change log here diff --git a/Project_FARSI/GITHUB_ISSUE_TEMPLATE_EXISTS.md b/Project_FARSI/GITHUB_ISSUE_TEMPLATE_EXISTS.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/GROUP_NOTIFICATIONS.md b/Project_FARSI/GROUP_NOTIFICATIONS.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/LICENSE b/Project_FARSI/LICENSE new file mode 100644 index 00000000..b3f94ae5 --- /dev/null +++ b/Project_FARSI/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Project_FARSI/PAGES_ENABLED.md b/Project_FARSI/PAGES_ENABLED.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/README.md b/Project_FARSI/README.md new file mode 100644 index 00000000..1b7e720c --- /dev/null +++ b/Project_FARSI/README.md @@ -0,0 +1,133 @@ +# Project_FARSI +FARSI is a agile pre-rtl design space exploration framework. It allows SOC designers to find optimal +designs given a set of constraints (performance/power/area and development cost). + + +## How Does it work +To solve the aforementioned problem, FARSI solves 3 problems simultaneously (figure bellow) using +3 main components: +* (1) A simulator to capture the behavior of the SOC. +* (2) An exploration heuristic to navigate the design space in an optimal fashion. +* (3) A database populated by the designer with workloads (e.g., hand tracking, ...), and the +possible hardware options (e.g., general purpose processors, accelerators, memory, etc). + +FARSI continuously samples the database to generate a sample design, simulates its fitness and uses its navigation heuristic to get closer to the optimal design. + +![alt text](figures/FARSI_methodology.png "FARSI components") +![alt text](figures/FARSI_output.png "FARSI Output") + +## Why FARSI +FARSI is developed to overcome the existing DSE problems such as scalability and performance. +To further clarify this, the figure below puts FARSI on the map compared to the other DSEs. +![alt text](figures/DSE_on_the_map.png "components") + + +## Building/Installing FARSI +FARSI is a python based source code. Hence, relevant python libraries need to be installed. + + +## FARSI Input +Software/hardware database shown above is used as an input to FARSI's framework. Here we briefly explain their functionality and encoding. + +**Software Database:** This includes labeled task dependency graphs (TDG). A task is the smallest optimization unit and is typically selected from the computationally intensive functions since they significantly impact the system behavior. TDG contains the dependency information between tasks, the number of instructions processed within a task, and the data movement between them. + +**Hardware Database**: This involves power, performance, and area estimation of each task for different hardware mappings (e.g., to general-purpose processors or specialized accelerators). + +### FARSI Input Encoding: +Although the semantics discussed above can be encoded and inputted in various formats, currently, our front-end parsers take them in the form of spreadsheets. Here we detail these sheets. Please note that examples of these sheets are provided in the specs/database_data/parsing folder. + +Each workload has its set of spreadsheet whose name starts with the $workload name$_database, e.g., audio_decoder_database. + +**Software Database Spreadsheets** + +*Task Data Movement:* contains information about the data movement between tasks and their execution dependency. This sheet is an adjacency matrix format, where the first row and the first column list the workload's tasks. The cell at the coordinate between two tasks shows the data movement among them. Note that data flows from the task shown in the row to the task shown in the column. Also, note that this format implies the execution dependency between tasks if said cells are non-empty. + +*Task instruction count:* contains information about each task's computation, specifically quantifying its non-memory instruction count. + +*Task Itr Count:* each task's loop iteration count. + +**Hardware Database Spreadsheets** + +*Task PE Performance:* Performance (in the number of cycles) associated with mapping of tasks to different processing elements (PEs). + +*Task PE Energy:* Energy associated with the accelerator running a task. + +*Task Area Performance:* Area associated with accelerators. + +*misc_database - Budget:* budget (power, performance, area) associated with various workloads. + +*misc_database - Block Characteristics:* contains information about the potential IPs used in the system. These are non-task specific IPs (as opposed to accelerators that are task-specific and whose information is provided in the TASK PE (AREA/Energy/Performance) spreadsheets.). + +*misc_database - Last Tasks.csv:* name of the last task within each workload. + +**Mapping Database Spreadsheets** + +*Hardware Graph:* contains information about how hardware components are connected. It's an adjacency matrix with the first row and the first column specifying the hardware block names. a **1** in the cell at the coordinate between two blocks indicates a connection between said blocks. + +*Task To Hardware Mapping:* contains information about which hardware blocks various tasks are mapped to. The first row specifies the hardware block names, and the first column specifies the software task names. If a task is mapped onto a hardware block, it is listed under that block. We follow two conventions within this spread sheet. + 1) Under the NoC and Memory blocks, direction of the accesses (read or write) needs to be specified, and this is denoted by an arrow **->**. + For example, a cell that contains **Task1 -> Task2** under a memory **M0** cell indicates that **Task1 data is written into M0 and furthermore, this data will be read by Task2 (as Task1's child)**. Please note that only the write direction is specified, and the read direction is implied from the writes, as was shown in the previous example. + 2) If multiple tasks are mapped to the same block, we separate them with a semicolon. + + +## Running FARSI + +### Stand Alone Simulation ### +The following commands allows the user to run the simulation in standalone mode. + +Switch into the bellow directory. +```shell +cd data_collection/collection_utils/sim_run/ +``` +Set the workload name in the simple_sim_run.py (by default we choose a simple workload) and run the simulation. + +```shell +python simple_sim_run.py +``` + +The output data will be provided under the data_collection/data/simple_sim_run/$date_time$ folder + + + +### Simulation + Exploration Heuristic ### +The following commands allow the user to run both the simulation and exploration simulatenously. + +Switch into bellow directory. +```shell +cd data_collection/collection_utils/what_ifs/ +``` +Set the workload name properly in FARSI_what_ifs_with_params.py (Select among, audio_decoder, hpvm_cava, and edge_detection) and run FARSI. + +```shell +python FARSI_what_ifs_with_params.py # run FARSI +``` + +PS: To modify the settings, modify the settings/config.py file. This file contains many knobs that will determine the exploration heuristic and simulation +features. Please refer to the in file documentations for more details + +output will be provided under data_collection/data/simple_run/$date_time$ + +## Main Contributors +Behzad Boroujerdian\ +Ying Jing + + +## How to Cite +@article{10.1145/3544016, +author = {Boroujerdian, Behzad and Jing, Ying and Tripathy, Devashree and Kumar, Amit and Subramanian, Lavanya and Yen, Luke and Lee, Vincent and Venkatesan, Vivek and Jindal, Amit and Shearer, Robert and Reddi, Vijay Janapa}, +title = {FARSI: An Early-Stage Design Space Exploration Framework to Tame the Domain-Specific System-on-Chip Complexity}, +year = {2022}, +publisher = {Association for Computing Machinery}, +url = {https://doi.org/10.1145/3544016}, +doi = {10.1145/3544016}, +journal = {ACM Trans. Embed. Comput. Syst.}, +month = {may} +} + + +## License +Copyright (c) Facebook, Inc. and its affiliates. +This source code is licensed under the MIT license found in the +LICENSE file in the root directory of this source tree. + + diff --git a/Project_FARSI/SHIP_IT_IS_ENABLED.md b/Project_FARSI/SHIP_IT_IS_ENABLED.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/SHIP_IT_IS_SET_UP.md b/Project_FARSI/SHIP_IT_IS_SET_UP.md new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/SIM_utils/SIM.py b/Project_FARSI/SIM_utils/SIM.py new file mode 100644 index 00000000..285d5673 --- /dev/null +++ b/Project_FARSI/SIM_utils/SIM.py @@ -0,0 +1,82 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. + +from SIM_utils.components.perf_sim import * +from SIM_utils.components.pow_sim import * +#from OSSIM_utils.components.pow_knob_sim import * +from design_utils.design import * +from settings import config + +# This module is our top level simulator containing all simulators (perf, and pow simulator) +class OSASimulator: + def __init__(self, dp, database, pk_dp=""): + self.time_elapsed = 0 # time elapsed from the beginning of the simulation + self.dp = dp # design point to simulate + self.perf_sim = PerformanceSimulator(self.dp) # performance simulator instance + self.pow_sim = PowerSimulator(self.dp) # power simulator instance + + self.database = database + if config.simulation_method == "power_knobs": + self.pk_dp = pk_dp + #self.knob_change_sim = PowerKnobSimulator(self.dp, self.pk_dp, self.database) + self.completion_time = -1 # time passed for the simulation to complete + self.program_status = "idle" + self.cur_tick_time = self.next_tick_time = 0 # current tick time + + # ------------------------------ + # Functionality: + # whether the simulation should terminate + # ------------------------------ + def terminate(self, program_status): + if config.termination_mode == "workload_completion": + return program_status == "done" + elif config.termination_mode == "time_budget_reahced": + return self.time_elapsed >= config.time_budge + else: + return False + + # ------------------------------ + # Functionality: + # ticking the simulation. Note that the tick time varies depending on what is (dynamically) happening in the + # system + # ------------------------------ + def tick(self): + self.cur_tick_time = self.next_tick_time + + # ------------------------------ + # Functionality + # progress the simulation for clock_time forward + # ------------------------------ + def step(self, clock_time): + self.next_tick_time, self.program_status = self.perf_sim.simulate(clock_time) + + # ------------------------------ + # Functionality: + # simulation + # ------------------------------ + def simulate(self): + blah = time.time() + while not self.terminate(self.program_status): + self.tick() + self.step(self.cur_tick_time) + + if config.use_cacti: + self.dp.correct_power_area_with_cacti(self.database) + + # collect all the stats upon completion of simulation + self.dp.collect_dp_stats(self.database) + + if config.simulation_method == "power_knobs": + self.knob_change_sim.launch() + + self.completion_time = self.next_tick_time + self.dp.set_serial_design_time(self.perf_sim.serial_latency) + self.dp.set_par_speedup(self.perf_sim.serial_latency/self.completion_time) + self.dp.set_simulation_time_analytical_portion(self.perf_sim.task_update_time + self.perf_sim.phase_interval_calc_time) + self.dp.set_simulation_time_phase_driven_portion(self.perf_sim.phase_scheduling_time) + self.dp.set_simulation_time_phase_calculation_portion(self.perf_sim.phase_interval_calc_time) + self.dp.set_simulation_time_task_update_portion(self.perf_sim.task_update_time) + self.dp.set_simulation_time_phase_scheduling_portion(self.perf_sim.phase_scheduling_time) + + return self.dp \ No newline at end of file diff --git a/Project_FARSI/SIM_utils/__init__.py b/Project_FARSI/SIM_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/SIM_utils/components/__init__.py b/Project_FARSI/SIM_utils/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/SIM_utils/components/perf_sim.py b/Project_FARSI/SIM_utils/components/perf_sim.py new file mode 100644 index 00000000..983c7ead --- /dev/null +++ b/Project_FARSI/SIM_utils/components/perf_sim.py @@ -0,0 +1,492 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. + +from design_utils.design import * +from functools import reduce + + +# This class is the performance simulator of FARSI +class PerformanceSimulator: + def __init__(self, sim_design): + self.design = sim_design # design to simulate + self.scheduled_kernels = [] # kernels already scheduled + self.driver_waiting_queue = [] # kernels whose trigger condition is met but can not run for various reasons + self.completed_kernels_for_memory_sizing = [] # kernels already completed + # List of all the kernels that are not scheduled yet (to be launched) + self.yet_to_schedule_kernels = self.design.get_kernels()[:] # kernels to be scheduled + self.all_kernels = self.yet_to_schedule_kernels[:] + self.task_token_queue = [] + self.old_clock_time = self.clock_time = 0 + self.program_status = "idle" # specifying the status of the program at the current tick + self.phase_num = -1 + self.krnl_latency_if_run_in_isolation = {} + self.serial_latency = 0 + self.workload_time_if_each_kernel_run_serially() + self.phase_interval_calc_time = 0 + self.phase_scheduling_time = 0 + self.task_update_time = 0 + + + def workload_time_if_each_kernel_run_serially(self): + self.serial_latency = 0 + for krnl in self.yet_to_schedule_kernels: + self.krnl_latency_if_run_in_isolation[krnl] = krnl.get_latency_if_krnel_run_in_isolation() + + for krnl, latency in self.krnl_latency_if_run_in_isolation.items(): + self.serial_latency += latency + + + def reset_perf_sim(self): + self.scheduled_kernels = [] + self.completed_kernels_for_memory_sizing = [] + # List of all the kernels that are not scheduled yet (to be launched) + self.yet_to_schedule_kernels = self.design.get_kernels()[:] + self.old_clock_time = self.clock_time = 0 + self.program_status = "idle" # specifying the status of the program at the current tick + self.phase_num = -1 + + + # ------------------------------ + # Functionality: + # tick the simulator clock_time forward + # ------------------------------ + def tick(self, clock_time): + self.clock_time = clock_time + + # ------------------------------ + # Functionality: + # find the next kernel to be scheduled time + # ------------------------------ + def next_kernel_to_be_scheduled_time(self): + timely_sorted_kernels = sorted(self.yet_to_schedule_kernels, key=lambda kernel: kernel.get_schedule().starting_time) + return timely_sorted_kernels[0].get_schedule().starting_time + + # ------------------------------ + # Functionality: + # convert the task to kernel + # ------------------------------ + def get_kernel_from_task(self, task): + for kernel in self.design.get_kernels()[:]: + if kernel.get_task() == task: + return kernel + raise Exception("kernel associated with task with name" + task.name + " is not found") + + # ------------------------------ + # Functionality: + # find the completion time of kernel that will be done the fastest + # ------------------------------ + def next_kernel_to_be_completed_time(self): + comp_time_list = [] # contains completion time of the running kernels + for kernel in self.scheduled_kernels: + comp_time_list.append(kernel.calc_kernel_completion_time()) + return min(comp_time_list) + self.clock_time + #else: + # return self.clock_time + """ + # ------------------------------ + # Functionality: + # all the dependencies of a kernel are done or no? + # ------------------------------ + def kernel_parents_all_done(self, kernel): + kernel_s_task = kernel.get_task() + parents_s_task = self.design.get_hardware_graph().get_task_graph().get_task_s_parents(kernel_s_task) + completed_tasks = [kernel.get_task() for kernel in self.completed_kernels_for_memory_sizing] + for task in parents_s_task: + if task not in completed_tasks: + return False + return True + """ + + def kernel_s_parents_done(self, krnl): + kernel_s_task = krnl.get_task() + parents_s_task = self.design.get_hardware_graph().get_task_graph().get_task_s_parents(kernel_s_task) + for parent in parents_s_task: + if not (parent, kernel_s_task) in self.task_token_queue: + return False + return True + + + # launch: Every iteration, we launch the kernel, i.e, + # we set the operating state appropriately, and size the hardware accordingly + def kernel_ready_to_be_launched(self, krnl): + if self.kernel_s_parents_done(krnl) and krnl not in self.scheduled_kernels and not self.krnl_done_iterating(krnl): + return True + return False + + def kernel_ready_to_fire(self, krnl): + if krnl.get_type() == "throughput_based" and krnl.throughput_time_trigger_achieved(self.clock_time): + return True + else: + return False + + def remove_parents_from_token_queue(self, krnl): + kernel_s_task = krnl.get_task() + parents_s_task = self.design.get_hardware_graph().get_task_graph().get_task_s_parents(kernel_s_task) + for parent in parents_s_task: + self.task_token_queue.remove((parent, kernel_s_task)) + + def krnl_done_iterating(self, krnl): + if krnl.iteration_ctr == -1 or krnl.iteration_ctr > 0: + return False + elif krnl.iteration_ctr == 0: + return True + + + # if multiple kernels running on the same PE's driver, + # only one can access it at a time. Thus, this function only keeps one on the PE. + def serialize_DMA(self): + # append waiting kernels and to be sch kernels to the already scheduled kernels + scheduled_kernels_tmp = [] + for el in self.scheduled_kernels: + scheduled_kernels_tmp.append(el) + for kernel in self.driver_waiting_queue: + scheduled_kernels_tmp.append(kernel) + + PE_blocks_used = [] + scheduled_kernels = [] + driver_waiting_queue = [] + for el in scheduled_kernels_tmp: + # only for read/write we serialize + if el.get_operating_state() in ["read", "write", "none"]: + pe = [blk for blk in el.get_blocks() if blk.type == "pe"][0] + if pe in PE_blocks_used: + driver_waiting_queue.append(el) + else: + scheduled_kernels.append(el) + PE_blocks_used.append(pe) + else: + scheduled_kernels.append(el) + return scheduled_kernels, driver_waiting_queue + + # ------------------------------ + # Functionality: + # Finds the kernels that are free to be scheduled (their parents are completed) + # ------------------------------ + def schedule_kernels_token_based(self): + for krnl in self.all_kernels: + if self.kernel_ready_to_be_launched(krnl): + # launch: Every iteration, we launch the kernel, i.e, + # we set the operating state appropriately, and size the hardware accordingly + self.kernel_s_parents_done(krnl) + self.remove_parents_from_token_queue(krnl) + self.scheduled_kernels.append(krnl) + if krnl in self.yet_to_schedule_kernels: + self.yet_to_schedule_kernels.remove(krnl) + + # initialize #insts, tick, and kernel progress status + krnl.launch(self.clock_time) + # update PE's that host the kernel + krnl.update_mem_size(1) + krnl.update_pe_size() + krnl.update_ic_size() + elif krnl.status == "in_progress" and not krnl.get_task().is_task_dummy() and self.kernel_ready_to_fire(krnl): + if krnl in self.scheduled_kernels: + print("a throughput based kernel was scheduled before it met its desired throughput. " + "This can cause issues in the models. Fix Later") + self.scheduled_kernels.append(krnl) + + # filter out kernels based on DMA serialization, i.e., only keep one kernel using the PE's driver. + if config.DMA_mode == "serialized_read_write": + self.scheduled_kernels, self.driver_waiting_queue = self.serialize_DMA() + + # ------------------------------ + # Functionality: + # Finds the kernels that are free to be scheduled (their parents are completed) + # ------------------------------ + def schedule_kernels(self): + if config.scheduling_policy == "FRFS": + kernels_to_schedule = [kernel_ for kernel_ in self.yet_to_schedule_kernels + if self.kernel_parents_all_done(kernel_)] + elif config.scheduling_policy == "time_based": + kernels_to_schedule = [kernel_ for kernel_ in self.yet_to_schedule_kernels + if self.clock_time >= kernel_.get_schedule().starting_time] + else: + raise Exception("scheduling policy not supported") + + for kernel in kernels_to_schedule: + self.scheduled_kernels.append(kernel) + self.yet_to_schedule_kernels.remove(kernel) + # initialize #insts, tick, and kernel progress status + kernel.launch(self.clock_time) + # update memory size -> allocate memory regions on different mem blocks + kernel.update_mem_size(1) + # update pe allocation -> allocate a part of pe quantum for current task + # (Hadi Note: allocation looks arbitrary and without any meaning though - just to know that something + # is allocated or it is floating) + kernel.update_pe_size() + # empty function! + kernel.update_ic_size() + + + def update_parallel_tasks(self): + # keep track of krnls that are present per phase + for krnl in self.scheduled_kernels: + if krnl.get_task_name() in ["souurce", "siink", "dummy_last"]: + continue + if krnl not in self.design.krnl_phase_present.keys(): + self.design.krnl_phase_present[krnl] = [] + self.design.krnl_phase_present_operating_state[krnl] = [] + self.design.krnl_phase_present[krnl].append(self.phase_num) + self.design.krnl_phase_present_operating_state[krnl].append((self.phase_num, krnl.operating_state)) + self.design.phase_krnl_present[self.phase_num] = self.scheduled_kernels[:] + + """ + scheduled_kernels = self.scheduled_kernels[:] + for idx, krnl in enumerate(scheduled_kernels): + if krnl.get_task_name() in ["souurce", "siink"]: + continue + if krnl not in self.design.parallel_kernels.keys(): + self.design.parallel_kernels[krnl] = [] + for idx, krnl_2 in enumerate(scheduled_kernels): + if krnl_2 == krnl: + continue + elif not krnl_2 in self.design.parallel_kernels[krnl]: + self.design.parallel_kernels[krnl].append(krnl_2) + + pass + """ + # ------------------------------ + # Functionality: + # update the status of each kernel, this means update + # how much work is left for each kernel (that is already schedulued) + # ------------------------------ + def update_scheduled_kernel_list(self): + scheduled_kernels = self.scheduled_kernels[:] + for kernel in scheduled_kernels: + if kernel.status == "completed": + self.scheduled_kernels.remove(kernel) + self.completed_kernels_for_memory_sizing.append(kernel) + kernel.set_stats() + for child_task in kernel.get_task().get_children(): + self.task_token_queue.append((kernel.get_task(), child_task)) + # iterate though parents and check if for each parent, all the children are completed. + # if so, retract the memory + all_parent_kernels = [self.get_kernel_from_task(parent_task) for parent_task in + kernel.get_task().get_parents()] + for parent_kernel in all_parent_kernels: + all_children_kernels = [self.get_kernel_from_task(child_task) for child_task in + parent_kernel.get_task().get_children()] + + if all([child_kernel in self.completed_kernels_for_memory_sizing for child_kernel in all_children_kernels]): + parent_kernel.update_mem_size(-1) + for child_kernel in all_children_kernels: + self.completed_kernels_for_memory_sizing.remove(child_kernel) + + elif kernel.type == "throughput_based" and kernel.throughput_work_achieved(): + #del kernel.data_work_left_to_meet_throughput[kernel.operating_state][0] + del kernel.firing_work_to_meet_throughput[kernel.operating_state][0] + self.scheduled_kernels.remove(kernel) + + + # ------------------------------ + # Functionality: + # iterate through all kernels and step them + # ------------------------------ + def step_kernels(self): + # by stepping the kernels, we calculate how much work each kernel has done and how much of their + # work is left for them + _ = [kernel_.step(self.time_step_size, self.phase_num) for kernel_ in self.scheduled_kernels] + + # update kernel's status, sets the progress + _ = [kernel_.update_status(self.time_step_size, self.clock_time) for kernel_ in self.scheduled_kernels] + + # ------------------------------ + # Functionality: + # update the status of the program, i.e., whether it's done or still in progress + # ------------------------------ + def update_program_status(self): + if len(self.scheduled_kernels) == 0 and len(self.yet_to_schedule_kernels) == 0: + self.program_status = "done" + elif len(self.scheduled_kernels) == 0: + self.program_status = "idle" # nothing scheduled yet + elif len(self.yet_to_schedule_kernels) == 0: + self.program_status = "all_kernels_scheduled" + else: + self.program_status = "in_progress" + + def next_throughput_trigger_time(self): + throughput_achieved_time_list = [] + for krnl in self.all_kernels: + if krnl.get_type() == "throughput_based" and krnl.status == "in_progress" \ + and not krnl.get_task().is_task_dummy() and not krnl.operating_state == "execute": + throughput_achieved_time_list.extend(krnl.firing_time_to_meet_throughput[krnl.operating_state]) + + throughput_achieved_time_list_filtered = [el for el in throughput_achieved_time_list if el> self.clock_time] + #time_sorted = sorted(throughput_achieved_time_list_filtered) + return throughput_achieved_time_list_filtered + + def any_throughput_based_kernel(self): + for krnl in self.all_kernels: + if krnl.get_type() == "throughput_based": + return True + return False + + # ------------------------------ + # Functionality: + # find the next tick time + # ------------------------------ + def calc_new_tick_position(self): + if config.scheduling_policy == "FRFS": + new_clock_list = [] + if len(self.scheduled_kernels) > 0: + new_clock_list.append(self.next_kernel_to_be_completed_time()) + + if self.any_throughput_based_kernel(): + trigger_time = self.next_throughput_trigger_time() + if len(trigger_time) > 0: + new_clock_list.append(min(trigger_time)) + + if len(new_clock_list) == 0: + return self.clock_time + else: + return min(new_clock_list) + #new_clock = max(new_clock, min(throughput_achieved_time)) + """ + elif self.program_status == "in_progress": + if self.program_status == "in_progress": + if config.scheudling_policy == "time_based": + new_clock = min(self.next_kernel_to_be_scheduled_time(), self.next_kernel_to_be_completed_time()) + elif self.program_status == "all_kernels_scheduled": + new_clock = self.next_kernel_to_be_completed_time() + elif self.program_status == "idle": + new_clock = self.next_kernel_to_be_scheduled_time() + if self.program_status == "done": + new_clock = self.clock_time + else: + raise Exception("scheduling policy:" + config.scheduling_policy + " is not supported") + """ + + #return new_clock + + # ------------------------------ + # Functionality: + # determine the various KPIs for each kernel. + # work-rate is how quickly each kernel can be done, which depends on it's bottleneck + # ------------------------------ + def update_kernels_kpi_for_next_tick(self, design): + # update each kernels's work-rate (bandwidth) + _ = [kernel.update_block_att_work_rate(self.scheduled_kernels) for kernel in self.scheduled_kernels] + # update each pipe cluster's paths (inpipe-outpipe) work-rate + + _ = [kernel.update_pipe_clusters_pathlet_work_rate() for kernel in self.scheduled_kernels] + # update each pipe cluster's paths (inpipe-outpipe) latency. Note that latency update must run after path's work-rate + + # update as it depends on it + # for fast simulation, ignore this + #_ = [kernel.update_path_latency() for kernel in self.scheduled_kernels] + #_ = [kernel.update_path_structural_latency(design) for kernel in self.scheduled_kernels] + + + # ------------------------------ + # Functionality: + # how much work does each block do for each phase + # ------------------------------ + def calc_design_work(self): + for SOC_type, SOC_id in self.design.get_designs_SOCs(): + blocks_seen = [] + for kernel in self.scheduled_kernels: + if kernel.SOC_type == SOC_type and kernel.SOC_id == SOC_id: + for block, work in kernel.block_phase_work_dict.items(): + if block not in blocks_seen : + blocks_seen.append(block) + #if block in self.block_phase_work_dict.keys(): + if self.phase_num in self.design.block_phase_work_dict[block].keys(): + self.design.block_phase_work_dict[block][self.phase_num] += work[self.phase_num] + else: + self.design.block_phase_work_dict[block][self.phase_num] = work[self.phase_num] + all_blocks = self.design.get_blocks() + for block in all_blocks: + if block in blocks_seen: + continue + self.design.block_phase_work_dict[block][self.phase_num] = 0 + + # ------------------------------ + # Functionality: + # calculate the utilization of each block in the design + # ------------------------------ + def calc_design_utilization(self): + for SOC_type, SOC_id in self.design.get_designs_SOCs(): + for block,phase_work in self.design.block_phase_work_dict.items(): + if self.design.phase_latency_dict[self.phase_num] == 0: + work_rate = 0 + else: + work_rate = (self.design.block_phase_work_dict[block][self.phase_num])/self.design.phase_latency_dict[self.phase_num] + self.design.block_phase_utilization_dict[block][self.phase_num] = work_rate/block.peak_work_rate + + # ------------------------------ + # Functionality: + # Aggregates the energy consumed for current phase over all the blocks + # ------------------------------ + def calc_design_energy(self): + for SOC_type, SOC_id in self.design.get_designs_SOCs(): + self.design.SOC_phase_energy_dict[(SOC_type, SOC_id)][self.phase_num] = \ + sum([kernel.stats.phase_energy_dict[self.phase_num] for kernel in self.scheduled_kernels + if kernel.SOC_type == SOC_type and kernel.SOC_id == SOC_id]) + if config.simulation_method == "power_knobs": + # Add up the leakage energy to the total energy consumption + # Please note the phase_leakage_energy_dict only counts for PE and IC energy (no mem included) + # since memory cannot be cut-off; otherwise will lose its contents + self.design.SOC_phase_energy_dict[(SOC_type, SOC_id)][self.phase_num] += \ + sum([kernel.stats.phase_leakage_energy_dict[self.phase_num] for kernel in self.scheduled_kernels + if kernel.SOC_type == SOC_type and kernel.SOC_id == SOC_id]) + + # Add the leakage power for memories + self.design.SOC_phase_energy_dict[(SOC_type, SOC_id)][self.phase_num] += \ + sum([block.get_leakage_power() * self.time_step_size for block in self.design.get_blocks() + if block.get_block_type_name() == "mem"]) + + # ------------------------------ + # Functionality: + # step the simulator forward, by moving all the kernels forward in time + # ------------------------------ + def step(self): + # the time step of the previous phase + self.time_step_size = self.clock_time - self.old_clock_time + # add the time step (time spent in the phase) to the design phase duration dictionary + self.design.phase_latency_dict[self.phase_num] = self.time_step_size + + # advance kernels + before_time = time.time() + self.step_kernels() + self.task_update_time += (time.time() - before_time) + + before_time = time.time() + # Aggregates the energy consumed for current phase over all the blocks + self.calc_design_energy() # needs be done after kernels have stepped, to aggregate their energy and divide + self.calc_design_work() # calculate how much work does each block do for this phase + self.calc_design_utilization() + self.phase_interval_calc_time += (time.time() - before_time) + + before_time = time.time() + self.update_scheduled_kernel_list() # if a kernel is done, schedule it out + self.phase_scheduling_time += (time.time() - before_time) + + #self.schedule_kernels() # schedule ready to be scheduled kernels + + self.schedule_kernels_token_based() + self.old_clock_time = self.clock_time # update clock + + # check if execution is completed or not! + self.update_program_status() + before_time = time.time() + self.update_kernels_kpi_for_next_tick(self.design) # update each kernels' work rate + self.phase_interval_calc_time += (time.time() - before_time) + + self.phase_num += 1 + self.update_parallel_tasks() + + # return the new tick position + before_time = time.time() + new_tick_position = self.calc_new_tick_position() + self.phase_interval_calc_time += (time.time() - before_time) + + return new_tick_position, self.program_status + + # ------------------------------ + # Functionality: + # call the simulator + # ------------------------------ + def simulate(self, clock_time): + self.tick(clock_time) + return self.step() \ No newline at end of file diff --git a/Project_FARSI/SIM_utils/components/pow_sim.py b/Project_FARSI/SIM_utils/components/pow_sim.py new file mode 100644 index 00000000..7aa9525b --- /dev/null +++ b/Project_FARSI/SIM_utils/components/pow_sim.py @@ -0,0 +1,21 @@ +#Copyright (c) Facebook, Inc. and its affiliates. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. + +class PowerSimulator(): + def __init__(self, design): + return None + + def power_model(self): + print("comming soon") + return 0 + + def step(self): + return 0 + + def tick(self, clock_time): + return 0 + + def simulate(self, clock_time): + self.tick(clock_time) + self.step() diff --git a/Project_FARSI/__init__.py b/Project_FARSI/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Project_FARSI/cacti_for_FARSI/2DDRAM_Samsung2GbDDR2.cfg b/Project_FARSI/cacti_for_FARSI/2DDRAM_Samsung2GbDDR2.cfg new file mode 100644 index 00000000..d035eae3 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/2DDRAM_Samsung2GbDDR2.cfg @@ -0,0 +1,194 @@ +# Cache size +//-size (bytes) 528 +//-size (bytes) 4096 +//-size (bytes) 262144 +//-size (bytes) 1048576 +//-size (bytes) 2097152 +//-size (bytes) 4194304 +//-size (bytes) 8388608 +//-size (bytes) 16777216 +//-size (bytes) 33554432 +//-size (bytes) 134217728 +//-size (bytes) 268435456 +//-size (bytes) 536870912 +//-size (bytes) 67108864 +//-size (bytes) 536870912 +//-size (bytes) 1073741824 +# For 3D DRAM memory please use Gb as units +-size (Gb) 2 + +# Line size +//-block size (bytes) 8 +-block size (bytes) 128 + +# To model Fully Associative cache, set associativity to zero +//-associativity 0 +//-associativity 2 +//-associativity 4 +-associativity 1 +//-associativity 16 + +-read-write port 1 +-exclusive read port 0 +-exclusive write port 0 +-single ended read ports 0 + +# Multiple banks connected using a bus +-UCA bank count 16 +//-technology (u) 0.032 +//-technology (u) 0.040 +//-technology (u) 0.065 +//-technology (u) 0.078 +-technology (u) 0.080 + +# following three parameters are meaningful only for main memories + +//-page size (bits) 8192 +-burst length 4 +-internal prefetch width 1 + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +//-Data array cell type - "itrs-hp" +//-Data array cell type - "itrs-lstp" +//-Data array cell type - "itrs-lop" +-Data array cell type - "comm-dram" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +//-Data array peripheral type - "itrs-hp" +-Data array peripheral type - "itrs-lstp" +//-Data array peripheral type - "itrs-lop" + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +-Tag array cell type - "itrs-hp" +//-Tag array cell type - "itrs-lstp" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +-Tag array peripheral type - "itrs-hp" +//-Tag array peripheral type - "itrs-lstp" + +# Bus width include data bits and address bits required by the decoder +//-output/input bus width 16 +//-output/input bus width 64 +-output/input bus width 64 + +// 300-400 in steps of 10 +-operating temperature (K) 350 + +# Type of memory - cache (with a tag array) or ram (scratch ram similar to a register file) +# or main memory (no tag array and every access will happen at a page granularity Ref: CACTI 5.3 report) +//-cache type "cache" +//-cache type "ram" +//-cache type "main memory" +-cache type "3D memory or 2D main memory" + +# Parameters for 3D DRAM +//-page size (bits) 16384 +-page size (bits) 8192 +//-page size (bits) 4096 +-burst depth 4 +-IO width 4 +-system frequency (MHz) 266 + +-stacked die count 1 +-partitioning granularity 0 // 0: coarse-grained rank-level; 1: fine-grained rank-level +//-TSV projection 1 // 0: ITRS aggressive; 1: industrial conservative + +## End of parameters for 3D DRAM + +# to model special structure like branch target buffers, directory, etc. +# change the tag size parameter +# if you want cacti to calculate the tagbits, set the tag size to "default" +-tag size (b) "default" +//-tag size (b) 45 + +# fast - data and tag access happen in parallel +# sequential - data array is accessed after accessing the tag array +# normal - data array lookup and tag access happen in parallel +# final data block is broadcasted in data array h-tree +# after getting the signal from the tag array +-access mode (normal, sequential, fast) - "fast" +//-access mode (normal, sequential, fast) - "normal" +//-access mode (normal, sequential, fast) - "sequential" + + +# DESIGN OBJECTIVE for UCA (or banks in NUCA) +-design objective (weight delay, dynamic power, leakage power, cycle time, area) 0:0:0:0:100 + +# Percentage deviation from the minimum value +# Ex: A deviation value of 10:1000:1000:1000:1000 will try to find an organization +# that compromises at most 10% delay. +# NOTE: Try reasonable values for % deviation. Inconsistent deviation +# percentage values will not produce any valid organizations. For example, +# 0:0:100:100:100 will try to identify an organization that has both +# least delay and dynamic power. Since such an organization is not possible, CACTI will +# throw an error. Refer CACTI-6 Technical report for more details +-deviate (delay, dynamic power, leakage power, cycle time, area) 50:100000:100000:100000:1000000 + +# Objective for NUCA +-NUCAdesign objective (weight delay, dynamic power, leakage power, cycle time, area) 0:0:0:0:100 +-NUCAdeviate (delay, dynamic power, leakage power, cycle time, area) 10:10000:10000:10000:10000 + +# Set optimize tag to ED or ED^2 to obtain a cache configuration optimized for +# energy-delay or energy-delay sq. product +# Note: Optimize tag will disable weight or deviate values mentioned above +# Set it to NONE to let weight and deviate values determine the +# appropriate cache configuration +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED" +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED^2" +-Optimize ED or ED^2 (ED, ED^2, NONE): "NONE" + +-Cache model (NUCA, UCA) - "UCA" +//-Cache model (NUCA, UCA) - "NUCA" + +# In order for CACTI to find the optimal NUCA bank value the following +# variable should be assigned 0. +-NUCA bank count 0 + +# NOTE: for nuca network frequency is set to a default value of +# 5GHz in time.c. CACTI automatically +# calculates the maximum possible frequency and downgrades this value if necessary + +# By default CACTI considers both full-swing and low-swing +# wires to find an optimal configuration. However, it is possible to +# restrict the search space by changing the signaling from "default" to +# "fullswing" or "lowswing" type. +-Wire signaling (fullswing, lowswing, default) - "Global_5" +//-Wire signaling (fullswing, lowswing, default) - "default" +//-Wire signaling (fullswing, lowswing, default) - "lowswing" + +//-Wire inside mat - "global" +-Wire inside mat - "semi-global" +-Wire outside mat - "global" +//-Wire outside mat - "semi-global" + +-Interconnect projection - "conservative" +//-Interconnect projection - "aggressive" + +# Contention in network (which is a function of core count and cache level) is one of +# the critical factor used for deciding the optimal bank count value +# core count can be 4, 8, or 16 +//-Core count 4 +-Core count 8 +//-Core count 16 +-Cache level (L2/L3) - "L3" + +-Add ECC - "false" + +//-Print level (DETAILED, CONCISE) - "CONCISE" +-Print level (DETAILED, CONCISE) - "DETAILED" + +# for debugging +//-Print input parameters - "true" +-Print input parameters - "false" +# force CACTI to model the cache with the +# following Ndbl, Ndwl, Nspd, Ndsam, +# and Ndcm values +-Force cache config - "true" +//-Force cache config - "false" +-Ndwl 128 +-Ndbl 32 +-Nspd 1 +-Ndcm 1 +-Ndsam1 1 +-Ndsam2 1 + diff --git a/Project_FARSI/cacti_for_FARSI/2DDRAM_micron1Gb.cfg b/Project_FARSI/cacti_for_FARSI/2DDRAM_micron1Gb.cfg new file mode 100644 index 00000000..4b94de4f --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/2DDRAM_micron1Gb.cfg @@ -0,0 +1,194 @@ +# Cache size +//-size (bytes) 528 +//-size (bytes) 4096 +//-size (bytes) 262144 +//-size (bytes) 1048576 +//-size (bytes) 2097152 +//-size (bytes) 4194304 +//-size (bytes) 8388608 +//-size (bytes) 16777216 +//-size (bytes) 33554432 +//-size (bytes) 134217728 +//-size (bytes) 268435456 +//-size (bytes) 536870912 +//-size (bytes) 67108864 +//-size (bytes) 536870912 +//-size (bytes) 1073741824 +# For 3D DRAM memory please use Gb as units +-size (Gb) 1 + +# Line size +//-block size (bytes) 8 +-block size (bytes) 128 + +# To model Fully Associative cache, set associativity to zero +//-associativity 0 +//-associativity 2 +//-associativity 4 +-associativity 1 +//-associativity 16 + +-read-write port 1 +-exclusive read port 0 +-exclusive write port 0 +-single ended read ports 0 + +# Multiple banks connected using a bus +-UCA bank count 8 +//-technology (u) 0.032 +//-technology (u) 0.040 +//-technology (u) 0.065 +-technology (u) 0.078 +//-technology (u) 0.080 + +# following three parameters are meaningful only for main memories + +//-page size (bits) 8192 +-burst length 4 +-internal prefetch width 1 + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +//-Data array cell type - "itrs-hp" +//-Data array cell type - "itrs-lstp" +//-Data array cell type - "itrs-lop" +-Data array cell type - "comm-dram" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +//-Data array peripheral type - "itrs-hp" +-Data array peripheral type - "itrs-lstp" +//-Data array peripheral type - "itrs-lop" + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +-Tag array cell type - "itrs-hp" +//-Tag array cell type - "itrs-lstp" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +-Tag array peripheral type - "itrs-hp" +//-Tag array peripheral type - "itrs-lstp" + +# Bus width include data bits and address bits required by the decoder +//-output/input bus width 16 +//-output/input bus width 64 +-output/input bus width 64 + +// 300-400 in steps of 10 +-operating temperature (K) 350 + +# Type of memory - cache (with a tag array) or ram (scratch ram similar to a register file) +# or main memory (no tag array and every access will happen at a page granularity Ref: CACTI 5.3 report) +//-cache type "cache" +//-cache type "ram" +//-cache type "main memory" +-cache type "3D memory or 2D main memory" + +## Parameters for 3D DRAM +-page size (bits) 16384 +//-page size (bits) 8192 +-burst depth 8 +-IO width 4 +-system frequency (MHz) 533 + +-stacked die count 1 +-partitioning granularity 0 // 0: coarse-grained rank-level; 1: fine-grained rank-level +//-TSV projection 1 // 0: ITRS aggressive; 1: industrial conservative + +## End of parameters for 3D DRAM + + +# to model special structure like branch target buffers, directory, etc. +# change the tag size parameter +# if you want cacti to calculate the tagbits, set the tag size to "default" +-tag size (b) "default" +//-tag size (b) 45 + +# fast - data and tag access happen in parallel +# sequential - data array is accessed after accessing the tag array +# normal - data array lookup and tag access happen in parallel +# final data block is broadcasted in data array h-tree +# after getting the signal from the tag array +-access mode (normal, sequential, fast) - "fast" +//-access mode (normal, sequential, fast) - "normal" +//-access mode (normal, sequential, fast) - "sequential" + + +# DESIGN OBJECTIVE for UCA (or banks in NUCA) +-design objective (weight delay, dynamic power, leakage power, cycle time, area) 0:0:0:0:10 + +# Percentage deviation from the minimum value +# Ex: A deviation value of 10:1000:1000:1000:1000 will try to find an organization +# that compromises at most 10% delay. +# NOTE: Try reasonable values for % deviation. Inconsistent deviation +# percentage values will not produce any valid organizations. For example, +# 0:0:100:100:100 will try to identify an organization that has both +# least delay and dynamic power. Since such an organization is not possible, CACTI will +# throw an error. Refer CACTI-6 Technical report for more details +-deviate (delay, dynamic power, leakage power, cycle time, area) 50:100000:100000:100000:1000000 + +# Objective for NUCA +-NUCAdesign objective (weight delay, dynamic power, leakage power, cycle time, area) 100:100:0:0:100 +-NUCAdeviate (delay, dynamic power, leakage power, cycle time, area) 10:10000:10000:10000:10000 + +# Set optimize tag to ED or ED^2 to obtain a cache configuration optimized for +# energy-delay or energy-delay sq. product +# Note: Optimize tag will disable weight or deviate values mentioned above +# Set it to NONE to let weight and deviate values determine the +# appropriate cache configuration +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED" +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED^2" +-Optimize ED or ED^2 (ED, ED^2, NONE): "NONE" + +-Cache model (NUCA, UCA) - "UCA" +//-Cache model (NUCA, UCA) - "NUCA" + +# In order for CACTI to find the optimal NUCA bank value the following +# variable should be assigned 0. +-NUCA bank count 0 + +# NOTE: for nuca network frequency is set to a default value of +# 5GHz in time.c. CACTI automatically +# calculates the maximum possible frequency and downgrades this value if necessary + +# By default CACTI considers both full-swing and low-swing +# wires to find an optimal configuration. However, it is possible to +# restrict the search space by changing the signaling from "default" to +# "fullswing" or "lowswing" type. +-Wire signaling (fullswing, lowswing, default) - "Global_30" +//-Wire signaling (fullswing, lowswing, default) - "default" +//-Wire signaling (fullswing, lowswing, default) - "lowswing" + +//-Wire inside mat - "global" +-Wire inside mat - "semi-global" +-Wire outside mat - "global" +//-Wire outside mat - "semi-global" + +-Interconnect projection - "conservative" +//-Interconnect projection - "aggressive" + +# Contention in network (which is a function of core count and cache level) is one of +# the critical factor used for deciding the optimal bank count value +# core count can be 4, 8, or 16 +//-Core count 4 +-Core count 8 +//-Core count 16 +-Cache level (L2/L3) - "L3" + +-Add ECC - "true" + +//-Print level (DETAILED, CONCISE) - "CONCISE" +-Print level (DETAILED, CONCISE) - "DETAILED" + +# for debugging +//-Print input parameters - "true" +-Print input parameters - "false" +# force CACTI to model the cache with the +# following Ndbl, Ndwl, Nspd, Ndsam, +# and Ndcm values +-Force cache config - "true" +//-Force cache config - "false" +-Ndwl 16 +-Ndbl 16 +-Nspd 1 +-Ndcm 1 +-Ndsam1 1 +-Ndsam2 1 + diff --git a/Project_FARSI/cacti_for_FARSI/3DDRAM_Samsung3D8Gb_extened.cfg b/Project_FARSI/cacti_for_FARSI/3DDRAM_Samsung3D8Gb_extened.cfg new file mode 100644 index 00000000..197bc21f --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/3DDRAM_Samsung3D8Gb_extened.cfg @@ -0,0 +1,197 @@ +# Cache size +//-size (bytes) 528 +//-size (bytes) 4096 +//-size (bytes) 262144 +//-size (bytes) 1048576 +//-size (bytes) 2097152 +//-size (bytes) 4194304 +//-size (bytes) 8388608 +//-size (bytes) 16777216 +//-size (bytes) 33554432 +//-size (bytes) 134217728 +//-size (bytes) 268435456 +//-size (bytes) 536870912 +//-size (bytes) 67108864 +//-size (bytes) 536870912 +//-size (bytes) 1073741824 +# For 3D DRAM memory please use Gb as units +-size (Gb) 8 + +# Line size +//-block size (bytes) 8 +-block size (bytes) 128 + +# To model Fully Associative cache, set associativity to zero +//-associativity 0 +//-associativity 2 +//-associativity 4 +-associativity 1 +//-associativity 16 + +-read-write port 1 +-exclusive read port 0 +-exclusive write port 0 +-single ended read ports 0 + +# Multiple banks connected using a bus +-UCA bank count 8 +//-technology (u) 0.032 +//-technology (u) 0.040 +//-technology (u) 0.065 +//-technology (u) 0.078 +//-technology (u) 0.080 +//-technology (u) 0.090 +-technology (u) 0.050 + +# following three parameters are meaningful only for main memories + +//-page size (bits) 8192 +-burst length 4 +-internal prefetch width 1 + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +//-Data array cell type - "itrs-hp" +//-Data array cell type - "itrs-lstp" +//-Data array cell type - "itrs-lop" +-Data array cell type - "comm-dram" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +//-Data array peripheral type - "itrs-hp" +-Data array peripheral type - "itrs-lstp" +//-Data array peripheral type - "itrs-lop" + +# following parameter can have one of five values -- (itrs-hp, itrs-lstp, itrs-lop, lp-dram, comm-dram) +-Tag array cell type - "itrs-hp" +//-Tag array cell type - "itrs-lstp" + +# following parameter can have one of three values -- (itrs-hp, itrs-lstp, itrs-lop) +-Tag array peripheral type - "itrs-hp" +//-Tag array peripheral type - "itrs-lstp" + +# Bus width include data bits and address bits required by the decoder +//-output/input bus width 16 +//-output/input bus width 64 +-output/input bus width 64 + +// 300-400 in steps of 10 +-operating temperature (K) 350 + +# Type of memory - cache (with a tag array) or ram (scratch ram similar to a register file) +# or main memory (no tag array and every access will happen at a page granularity Ref: CACTI 5.3 report) +//-cache type "cache" +//-cache type "ram" +//-cache type "main memory" # old main memory model, in fact, it is eDRAM model. +-cache type "3D memory or 2D main memory" # once this parameter is used, the new parameter section below of will override the same parameter above + +# +//-page size (bits) 16384 +-page size (bits) 8192 +//-page size (bits) 4096 +-burst depth 8 // for 3D DRAM, IO per bank equals the product of burst depth and IO width +-IO width 4 +-system frequency (MHz) 677 + +-stacked die count 4 +-partitioning granularity 0 // 0: coarse-grained rank-level; 1: fine-grained rank-level +-TSV projection 1 // 0: ITRS aggressive; 1: industrial conservative + +## End of parameters for 3D DRAM + +# to model special structure like branch target buffers, directory, etc. +# change the tag size parameter +# if you want cacti to calculate the tagbits, set the tag size to "default" +-tag size (b) "default" +//-tag size (b) 45 + +# fast - data and tag access happen in parallel +# sequential - data array is accessed after accessing the tag array +# normal - data array lookup and tag access happen in parallel +# final data block is broadcasted in data array h-tree +# after getting the signal from the tag array +-access mode (normal, sequential, fast) - "fast" +//-access mode (normal, sequential, fast) - "normal" +//-access mode (normal, sequential, fast) - "sequential" + + +# DESIGN OBJECTIVE for UCA (or banks in NUCA) +-design objective (weight delay, dynamic power, leakage power, cycle time, area) 0:0:0:0:100 + +# Percentage deviation from the minimum value +# Ex: A deviation value of 10:1000:1000:1000:1000 will try to find an organization +# that compromises at most 10% delay. +# NOTE: Try reasonable values for % deviation. Inconsistent deviation +# percentage values will not produce any valid organizations. For example, +# 0:0:100:100:100 will try to identify an organization that has both +# least delay and dynamic power. Since such an organization is not possible, CACTI will +# throw an error. Refer CACTI-6 Technical report for more details +-deviate (delay, dynamic power, leakage power, cycle time, area) 50:100000:100000:100000:1000000 + +# Objective for NUCA +-NUCAdesign objective (weight delay, dynamic power, leakage power, cycle time, area) 0:0:0:0:100 +-NUCAdeviate (delay, dynamic power, leakage power, cycle time, area) 10:10000:10000:10000:10000 + +# Set optimize tag to ED or ED^2 to obtain a cache configuration optimized for +# energy-delay or energy-delay sq. product +# Note: Optimize tag will disable weight or deviate values mentioned above +# Set it to NONE to let weight and deviate values determine the +# appropriate cache configuration +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED" +//-Optimize ED or ED^2 (ED, ED^2, NONE): "ED^2" +-Optimize ED or ED^2 (ED, ED^2, NONE): "NONE" + +-Cache model (NUCA, UCA) - "UCA" +//-Cache model (NUCA, UCA) - "NUCA" + +# In order for CACTI to find the optimal NUCA bank value the following +# variable should be assigned 0. +-NUCA bank count 0 + +# NOTE: for nuca network frequency is set to a default value of +# 5GHz in time.c. CACTI automatically +# calculates the maximum possible frequency and downgrades this value if necessary + +# By default CACTI considers both full-swing and low-swing +# wires to find an optimal configuration. However, it is possible to +# restrict the search space by changing the signaling from "default" to +# "fullswing" or "lowswing" type. +-Wire signaling (fullswing, lowswing, default) - "Global_30" +//-Wire signaling (fullswing, lowswing, default) - "default" +//-Wire signaling (fullswing, lowswing, default) - "lowswing" + +//-Wire inside mat - "global" +-Wire inside mat - "semi-global" +-Wire outside mat - "global" +//-Wire outside mat - "semi-global" + +-Interconnect projection - "conservative" +//-Interconnect projection - "aggressive" + +# Contention in network (which is a function of core count and cache level) is one of +# the critical factor used for deciding the optimal bank count value +# core count can be 4, 8, or 16 +//-Core count 4 +-Core count 8 +//-Core count 16 +-Cache level (L2/L3) - "L3" + +-Add ECC - "true" + +//-Print level (DETAILED, CONCISE) - "CONCISE" +-Print level (DETAILED, CONCISE) - "DETAILED" + + +# for debugging +//-Print input parameters - "true" +-Print input parameters - "false" +# force CACTI to model the cache with the +# following Ndbl, Ndwl, Nspd, Ndsam, +# and Ndcm values +-Force cache config - "true" +//-Force cache config - "false" +-Ndwl 16 +-Ndbl 32 +-Nspd 1 +-Ndcm 1 +-Ndsam1 1 +-Ndsam2 1 + diff --git a/Project_FARSI/cacti_for_FARSI/README b/Project_FARSI/cacti_for_FARSI/README new file mode 100644 index 00000000..0dc88f52 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/README @@ -0,0 +1,122 @@ +----------------------------------------------------------- + + + ____ __ ________ __ + /\ _`\ /\ \__ __ /\_____ \ /'__`\ + \ \ \/\_\ __ ___\ \ ,_\/\_\ \/___//'/'/\ \/\ \ + \ \ \/_/_ /'__`\ /'___\ \ \/\/\ \ /' /' \ \ \ \ \ + \ \ \L\ \/\ \L\.\_/\ \__/\ \ \_\ \ \ /' /'__ \ \ \_\ \ + \ \____/\ \__/.\_\ \____\\ \__\\ \_\ /\_/ /\_\ \ \____/ + \/___/ \/__/\/_/\/____/ \/__/ \/_/ \// \/_/ \/___/ + + +A Tool to Model Caches/Memories, 3D stacking, and off-chip IO +----------------------------------------------------------- + +CACTI is an analytical tool that takes a set of cache/memory para- +meters as input and calculates its access time, power, cycle +time, and area. +CACTI was originally developed by Dr. Jouppi and Dr. Wilton +in 1993 and since then it has undergone six major +revisions. + +List of features (version 1-7): +=============================== +The following is the list of features supported by the tool. + +* Power, delay, area, and cycle time model for + direct mapped caches + set-associative caches + fully associative caches + Embedded DRAM memories + Commodity DRAM memories + +* Support for modeling multi-ported uniform cache access (UCA) + and multi-banked, multi-ported non-uniform cache access (NUCA). + +* Leakage power calculation that also considers the operating + temperature of the cache. + +* Router power model. + +* Interconnect model with different delay, power, and area + properties including low-swing wire model. + +* An interface to perform trade-off analysis involving power, delay, + area, and bandwidth. + +* All process specific values used by the tool are obtained + from ITRS and currently, the tool supports 90nm, 65nm, 45nm, + and 32nm technology nodes. + +* Chip IO model to calculate latency and energy for DDR bus. Users can model + different loads (fan-outs) and evaluate the impact on frequency and energy. + This model can be used to study LR-DIMMs, R-DIMMs, etc. + +Version 7.0 is derived from 6.5 and merged with CACTI 3D. +It has many new additions apart from code refinements and +bug fixes: new IO model, 3D memory model, and power gating models. +Ref: CACTI-IO: CACTI With OFF-chip Power-Area-Timing Models + MemCAD: An Interconnect Exploratory Tool for Innovative Memories Beyond DDR4 + CACTI-3DD: Architecture-level modeling for 3D die-stacked DRAM main memory + +-------------------------------------------------------------------------- +Version 6.5 has a new c++ code base and includes numerous bug fixes. +CACTI 5.3 and 6.0 activate an entire row of mats to read/write a single +block of data. This technique improves reliability at the cost of +power. CACTI 6.5 activates minimum number of mats just enough to retrieve +a block to minimize power. + +How to use the tool? +==================== +Prior versions of CACTI take input parameters such as cache +size and technology node as a set of command line arguments. +To avoid a long list of command line arguments, +CACTI 6.5 & & let users specify their cache model in a more +detailed manner by using a config file (cache.cfg). + +-> define the cache model using cache.cfg +-> run the "cacti" binary <./cacti -infile cache.cfg> + +CACTI also provides a command line interface similar to earlier versions. The command line interface can be used as + +./cacti cache_size line_size associativity rw_ports excl_read_ports excl_write_ports + single_ended_read_ports search_ports banks tech_node output_width specific_tag tag_width + access_mode cache main_mem obj_func_delay obj_func_dynamic_power obj_func_leakage_power + obj_func_cycle_time obj_func_area dev_func_delay dev_func_dynamic_power dev_func_leakage_power + dev_func_area dev_func_cycle_time ed_ed2_none temp wt data_arr_ram_cell_tech_flavor_in + data_arr_peri_global_tech_flavor_in tag_arr_ram_cell_tech_flavor_in tag_arr_peri_global_tech_flavor_in + interconnect_projection_type_in wire_inside_mat_type_in wire_outside_mat_type_in + REPEATERS_IN_HTREE_SEGMENTS_in VERTICAL_HTREE_WIRES_OVER_THE_ARRAY_in + BROADCAST_ADDR_DATAIN_OVER_VERTICAL_HTREES_in PAGE_SIZE_BITS_in BURST_LENGTH_in + INTERNAL_PREFETCH_WIDTH_in force_wiretype wiretype force_config ndwl ndbl nspd ndcm + ndsam1 ndsam2 ecc + +For complete documentation of the tool, please refer +to the following publications and reports. + +CACTI-5.3 & 6 reports - Details on Meory/cache organizations and tradeoffs. + +Latency/Energy tradeoffs for large caches and NUCA design: + "Optimizing NUCA Organizations and Wiring Alternatives for Large Caches With CACTI 6.0", that appears in MICRO 2007. + +Memory IO design: CACTI-IO: CACTI With OFF-chip Power-Area-Timing Models, + MemCAD: An Interconnect Exploratory Tool for Innovative Memories Beyond DDR4 + CACTI-IO Technical Report - http://www.hpl.hp.com/techreports/2013/HPL-2013-79.pdf + +3D model: + CACTI-3DD: Architecture-level modeling for 3D die-stacked DRAM main memory + +We are still improving the tool and refining the code. If you +have any comments, questions, or suggestions please write to +us. + +Naveen Muralimanohar +naveen.muralimanohar@hpe.com + +Ali Shafiee +shafiee@cs.utah.edu + +Vaishnav Srinivas +vaishnav.srinivas@gmail.com + diff --git a/Project_FARSI/cacti_for_FARSI/TSV.cc b/Project_FARSI/cacti_for_FARSI/TSV.cc new file mode 100644 index 00000000..2821d4bd --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/TSV.cc @@ -0,0 +1,242 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + +#include "TSV.h" + +TSV::TSV(enum TSV_type tsv_type, + /*TechnologyParameter::*/DeviceType *dt)://TSV driver's device type set to be peri_global + deviceType(dt), tsv_type(tsv_type) +{ + num_gates = 1; + num_gates_min = 1;//Is there a minimum number of stages? + min_w_pmos = deviceType -> n_to_p_eff_curr_drv_ratio * g_tp.min_w_nmos_; + + switch (tsv_type) + { + case Fine: + cap = g_tp.tsv_parasitic_capacitance_fine; + res = g_tp.tsv_parasitic_resistance_fine; + min_area = g_tp.tsv_minimum_area_fine; + break; + case Coarse: + cap = g_tp.tsv_parasitic_capacitance_coarse; + res = g_tp.tsv_parasitic_resistance_coarse; + min_area = g_tp.tsv_minimum_area_coarse; + break; + default: + break; + } + + for (int i = 0; i < MAX_NUMBER_GATES_STAGE; i++) + { + w_TSV_n[i] = 0; + w_TSV_p[i] = 0; + } + + double first_buf_stg_coef = 5; // To tune the total buffer delay. + w_TSV_n[0] = g_tp.min_w_nmos_*first_buf_stg_coef; + w_TSV_p[0] = min_w_pmos *first_buf_stg_coef; + + is_dram = 0; + is_wl_tr = 0; + + //What does the function assert() mean? Should I put the function here? + compute_buffer_stage(); + compute_area(); + compute_delay(); +} + +TSV::~TSV() +{ +} + +void TSV::compute_buffer_stage() +{ + double p_to_n_sz_ratio = deviceType->n_to_p_eff_curr_drv_ratio; + + //BEOL parasitics in Katti's E modeling and charac. of TSV. Needs further detailed values. + //double res_beol = 0.1;//inaccurate + //double cap_beol = 1e-15; + + //C_load_TSV = cap_beol + cap + cap_beol + gate_C(g_tp.min_w_nmos_ + min_w_pmos, 0); + C_load_TSV = cap + gate_C(g_tp.min_w_nmos_ + min_w_pmos, 0); //+ 57.5e-15; + if(g_ip->print_detail_debug) + { + cout << " The input cap of 1st buffer: " << gate_C(w_TSV_n[0] + w_TSV_p[0], 0) * 1e15 << " fF"; + } + double F = C_load_TSV / gate_C(w_TSV_n[0] + w_TSV_p[0], 0); + if(g_ip->print_detail_debug) + { + cout<<"\nF is "<Vdd; + double cumulative_area = 0; + double cumulative_curr = 0; // cumulative leakage current + double cumulative_curr_Ig = 0; // cumulative leakage current + Buffer_area.h = g_tp.cell_h_def;//cell_h_def is the assigned height for memory cell (5um), is it correct to use it here? + + //logic_effort() didn't give the size of w_n[0] and w_p[0], which is min size inverter + //w_TSV_n[0] = g_tp.min_w_nmos_; + //w_TSV_p[0] = min_w_pmos; + + int i; + for (i = 0; i < num_gates; i++) + { + cumulative_area += compute_gate_area(INV, 1, w_TSV_p[i], w_TSV_n[i], Buffer_area.h); + if(g_ip->print_detail_debug) + { + cout << "\n\tArea up to the " << i+1 << " stages is: " << cumulative_area << " um2"; + } + cumulative_curr += cmos_Isub_leakage(w_TSV_n[i], w_TSV_p[i], 1, inv, is_dram); + cumulative_curr_Ig += cmos_Ig_leakage(w_TSV_n[i], w_TSV_p[i], 1, inv, is_dram);// The operator += is mistakenly put as = in decoder.cc + } + power.readOp.leakage = cumulative_curr * Vdd; + power.readOp.gate_leakage = cumulative_curr_Ig * Vdd; + + Buffer_area.set_area(cumulative_area); + Buffer_area.w = (cumulative_area / Buffer_area.h); + + TSV_metal_area.set_area(min_area * 3.1416/16); + + if( Buffer_area.get_area() < min_area - TSV_metal_area.get_area() ) + area.set_area(min_area); + else + area.set_area(Buffer_area.get_area() + TSV_metal_area.get_area()); + +} + +void TSV::compute_delay() +{ + //Buffer chain delay and Dynamic Power + double rd, tf, this_delay, c_load, c_intrinsic, inrisetime = 0/*The initial time*/; + //is_dram, is_wl_tr are declared to be false in the constructor + rd = tr_R_on(w_TSV_n[0], NCH, 1, is_dram, false, is_wl_tr); + c_load = gate_C(w_TSV_n[1] + w_TSV_p[1], 0.0, is_dram, false, is_wl_tr); + c_intrinsic = drain_C_(w_TSV_p[0], PCH, 1, 1, area.h, is_dram, false, is_wl_tr) + + drain_C_(w_TSV_n[0], NCH, 1, 1, area.h, is_dram, false, is_wl_tr); + tf = rd * (c_intrinsic + c_load); + //Refer to horowitz function definition + this_delay = horowitz(inrisetime, tf, 0.5, 0.5, RISE); + delay += this_delay; + inrisetime = this_delay / (1.0 - 0.5); + + double Vdd = deviceType -> Vdd; + power.readOp.dynamic += (c_load + c_intrinsic) * Vdd * Vdd; + + int i; + for (i = 1; i < num_gates - 1; ++i) + { + rd = tr_R_on(w_TSV_n[i], NCH, 1, is_dram, false, is_wl_tr); + c_load = gate_C(w_TSV_p[i+1] + w_TSV_n[i+1], 0.0, is_dram, false, is_wl_tr); + c_intrinsic = drain_C_(w_TSV_p[i], PCH, 1, 1, area.h, is_dram, false, is_wl_tr) + + drain_C_(w_TSV_n[i], NCH, 1, 1, area.h, is_dram, false, is_wl_tr); + tf = rd * (c_intrinsic + c_load); + this_delay = horowitz(inrisetime, tf, 0.5, 0.5, RISE); + delay += this_delay; + inrisetime = this_delay / (1.0 - 0.5); + power.readOp.dynamic += (c_load + c_intrinsic) * Vdd * Vdd; + } + + // add delay of final inverter that drives the TSV + i = num_gates - 1; + c_load = C_load_TSV; + rd = tr_R_on(w_TSV_n[i], NCH, 1, is_dram, false, is_wl_tr); + c_intrinsic = drain_C_(w_TSV_p[i], PCH, 1, 1, area.h, is_dram, false, is_wl_tr) + + drain_C_(w_TSV_n[i], NCH, 1, 1, area.h, is_dram, false, is_wl_tr); + //The delay method for the last stage of buffer chain in Decoder.cc + + //double res_beol = 0.1;//inaccurate + //double R_TSV_out = res_beol + res + res_beol; + double R_TSV_out = res; + tf = rd * (c_intrinsic + c_load) + R_TSV_out * c_load / 2; + this_delay = horowitz(inrisetime, tf, 0.5, 0.5, RISE); + delay += this_delay; + + power.readOp.dynamic += (c_load + c_intrinsic) * Vdd * Vdd; //Dynamic power done + + //Is the delay actually delay/(1.0-0.5)?? + //ret_val = this_delay / (1.0 - 0.5); + //return ret_val;//Originally for decoder.cc to get outrise time + + + /* This part is to obtain delay in the TSV path, refer to Katti's paper. + * It can be used alternatively as the step to get the final-stage delay + double C_ext = c_intrinsic; + R_dr = rd; + double C_int = gate_C(g_tp.min_w_nmos_ + min_w_pmos, 0.0, is_dram, false, is_wl_tr); + delay_TSV_path = 0.693 * (R_dr * C_ext + (R_dr + res_beol) * cap_beol + (R_dr + res_beol + 0.5 * res) * cap + + (R_dr + res_beol + res + res_beol) * (cap_beol + C_int); + delay += delay_TSV_path; + */ +} + +void TSV::print_TSV() +{ + + cout << "\nTSV Properties:\n\n"; + cout << " Delay Optimal - "<< + " \n\tTSV Cap: " << cap * 1e15 << " fF" << + " \n\tTSV Res: " << res * 1e3 << " mOhm"<< + " \n\tNumber of Buffer Chain stages - " << num_gates << + " \n\tDelay - " << delay * 1e9 << " (ns) " + " \n\tPowerD - " << power.readOp.dynamic * 1e9<< " (nJ)" + " \n\tPowerL - " << power.readOp.leakage * 1e3<< " (mW)" + " \n\tPowerLgate - " << power.readOp.gate_leakage * 1e3<< " (mW)\n" << + " \n\tBuffer Area: " << Buffer_area.get_area() << " um2" << + " \n\tBuffer Height: " << Buffer_area.h << " um" << + " \n\tBuffer Width: " << Buffer_area.w << " um" << + " \n\tTSV metal area: " << TSV_metal_area.get_area() << " um2" << + " \n\tTSV minimum occupied area: " < +#include +#include + + +class TSV : public Component +{ + public: + TSV(enum TSV_type tsv_type, + /*TechnologyParameter::*/DeviceType * dt = &(g_tp.peri_global));//Should change peri_global to TSV in technology.cc + //TSV():len(20),rad(2.5),pitch(50){} + ~TSV(); + + double res;//TSV resistance + double cap;//TSV capacitance + double C_load_TSV;//The intrinsic load plus the load TSV is driving, needs changes? + double min_area; + + //int num_IO;//number of I/O + int num_gates; + int num_gates_min;//Necessary? + double w_TSV_n[MAX_NUMBER_GATES_STAGE]; + double w_TSV_p[MAX_NUMBER_GATES_STAGE]; + + //double delay_TSV_path;//Delay of TSV path including the parasitics + + double is_dram;//two external arguments, defaulted to be false in constructor + double is_wl_tr; + + void compute_buffer_stage(); + void compute_area(); + void compute_delay(); + void print_TSV(); + + Area TSV_metal_area; + Area Buffer_area; + + /*//Herigated from Component + double delay; + Area area; + powerDef power, rt_power; + double delay; + double cycle_time; + + int logical_effort();*/ + + private: + double min_w_pmos; + /*TechnologyParameter::*/DeviceType * deviceType; + unsigned int tsv_type; + +}; + + +#endif /* TSV_H_ */ diff --git a/Project_FARSI/cacti_for_FARSI/Ucache.cc b/Project_FARSI/cacti_for_FARSI/Ucache.cc new file mode 100644 index 00000000..7df02079 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/Ucache.cc @@ -0,0 +1,1073 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + +#include +#include + + +#include "area.h" +#include "bank.h" +#include "basic_circuit.h" +#include "component.h" +#include "const.h" +#include "decoder.h" +#include "parameter.h" +#include "Ucache.h" +#include "subarray.h" +#include "uca.h" + +#include +#include +#include +#include + +using namespace std; + +const uint32_t nthreads = NTHREADS; + + +void min_values_t::update_min_values(const min_values_t * val) +{ + min_delay = (min_delay > val->min_delay) ? val->min_delay : min_delay; + min_dyn = (min_dyn > val->min_dyn) ? val->min_dyn : min_dyn; + min_leakage = (min_leakage > val->min_leakage) ? val->min_leakage : min_leakage; + min_area = (min_area > val->min_area) ? val->min_area : min_area; + min_cyc = (min_cyc > val->min_cyc) ? val->min_cyc : min_cyc; +} + + + +void min_values_t::update_min_values(const uca_org_t & res) +{ + min_delay = (min_delay > res.access_time) ? res.access_time : min_delay; + min_dyn = (min_dyn > res.power.readOp.dynamic) ? res.power.readOp.dynamic : min_dyn; + min_leakage = (min_leakage > res.power.readOp.leakage) ? res.power.readOp.leakage : min_leakage; + min_area = (min_area > res.area) ? res.area : min_area; + min_cyc = (min_cyc > res.cycle_time) ? res.cycle_time : min_cyc; +} + +void min_values_t::update_min_values(const nuca_org_t * res) +{ + min_delay = (min_delay > res->nuca_pda.delay) ? res->nuca_pda.delay : min_delay; + min_dyn = (min_dyn > res->nuca_pda.power.readOp.dynamic) ? res->nuca_pda.power.readOp.dynamic : min_dyn; + min_leakage = (min_leakage > res->nuca_pda.power.readOp.leakage) ? res->nuca_pda.power.readOp.leakage : min_leakage; + min_area = (min_area > res->nuca_pda.area.get_area()) ? res->nuca_pda.area.get_area() : min_area; + min_cyc = (min_cyc > res->nuca_pda.cycle_time) ? res->nuca_pda.cycle_time : min_cyc; +} + +void min_values_t::update_min_values(const mem_array * res) +{ + min_delay = (min_delay > res->access_time) ? res->access_time : min_delay; + min_dyn = (min_dyn > res->power.readOp.dynamic) ? res->power.readOp.dynamic : min_dyn; + min_leakage = (min_leakage > res->power.readOp.leakage) ? res->power.readOp.leakage : min_leakage; + min_area = (min_area > res->area) ? res->area : min_area; + min_cyc = (min_cyc > res->cycle_time) ? res->cycle_time : min_cyc; +} + + + +void * calc_time_mt_wrapper(void * void_obj) +{ + calc_time_mt_wrapper_struct * calc_obj = (calc_time_mt_wrapper_struct *) void_obj; + uint32_t tid = calc_obj->tid; + list & data_arr = calc_obj->data_arr; + list & tag_arr = calc_obj->tag_arr; + bool is_tag = calc_obj->is_tag; + bool pure_ram = calc_obj->pure_ram; + bool pure_cam = calc_obj->pure_cam; + bool is_main_mem = calc_obj->is_main_mem; + double Nspd_min = calc_obj->Nspd_min; + min_values_t * data_res = calc_obj->data_res; + min_values_t * tag_res = calc_obj->tag_res; + + data_arr.clear(); + data_arr.push_back(new mem_array); + tag_arr.clear(); + tag_arr.push_back(new mem_array); + + uint32_t Ndwl_niter = _log2(MAXDATAN) + 1; + uint32_t Ndbl_niter = _log2(MAXDATAN) + 1; + uint32_t Ndcm_niter = _log2(MAX_COL_MUX) + 1; + uint32_t niter = Ndwl_niter * Ndbl_niter * Ndcm_niter; + + + bool is_valid_partition; + int wt_min, wt_max; + + if (g_ip->force_wiretype) { + if (g_ip->wt == Full_swing) { + wt_min = Global; + wt_max = Low_swing-1; + } + else { + switch(g_ip->wt) { + case Global: + wt_min = wt_max = Global; + break; + case Global_5: + wt_min = wt_max = Global_5; + break; + case Global_10: + wt_min = wt_max = Global_10; + break; + case Global_20: + wt_min = wt_max = Global_20; + break; + case Global_30: + wt_min = wt_max = Global_30; + break; + case Low_swing: + wt_min = wt_max = Low_swing; + break; + default: + cerr << "Unknown wire type!\n"; + exit(0); + } + } + } + else { + wt_min = Global; + wt_max = Low_swing; + } + + for (double Nspd = Nspd_min; Nspd <= MAXDATASPD; Nspd *= 2) + { + for (int wr = wt_min; wr <= wt_max; wr++) + { + for (uint32_t iter = tid; iter < niter; iter += nthreads) + { + // reconstruct Ndwl, Ndbl, Ndcm + unsigned int Ndwl = 1 << (iter / (Ndbl_niter * Ndcm_niter)); + unsigned int Ndbl = 1 << ((iter / (Ndcm_niter))%Ndbl_niter); + unsigned int Ndcm = 1 << (iter % Ndcm_niter); + for(unsigned int Ndsam_lev_1 = 1; Ndsam_lev_1 <= MAX_COL_MUX; Ndsam_lev_1 *= 2) + { + for(unsigned int Ndsam_lev_2 = 1; Ndsam_lev_2 <= MAX_COL_MUX; Ndsam_lev_2 *= 2) + { + //for debuging + if (g_ip->force_cache_config && is_tag == false) + { + wr = g_ip->wt; + Ndwl = g_ip->ndwl; + Ndbl = g_ip->ndbl; + Ndcm = g_ip->ndcm; + if(g_ip->nspd != 0) { + Nspd = g_ip->nspd; + } + if(g_ip->ndsam1 != 0) { + Ndsam_lev_1 = g_ip->ndsam1; + Ndsam_lev_2 = g_ip->ndsam2; + } + } + + if (is_tag == true) + { + is_valid_partition = calculate_time(is_tag, pure_ram, pure_cam, Nspd, Ndwl, + Ndbl, Ndcm, Ndsam_lev_1, Ndsam_lev_2, + tag_arr.back(), 0, NULL, NULL,(Wire_type) wr, + is_main_mem); + } + // If it's a fully-associative cache, the data array partition parameters are identical to that of + // the tag array, so compute data array partition properties also here. + if (is_tag == false || g_ip->fully_assoc) + { + is_valid_partition = calculate_time(is_tag/*false*/, pure_ram, pure_cam, Nspd, Ndwl, + Ndbl, Ndcm, Ndsam_lev_1, Ndsam_lev_2, + data_arr.back(), 0, NULL, NULL,(Wire_type) wr, + is_main_mem); + if (g_ip->is_3d_mem) + { + Ndsam_lev_1 = MAX_COL_MUX+1; + Ndsam_lev_2 = MAX_COL_MUX+1; + } + } + + if (is_valid_partition) + { + if (is_tag == true) + { + tag_arr.back()->wt = (enum Wire_type) wr; + tag_res->update_min_values(tag_arr.back()); + tag_arr.push_back(new mem_array); + } + if (is_tag == false || g_ip->fully_assoc) + { + data_arr.back()->wt = (enum Wire_type) wr; + data_res->update_min_values(data_arr.back()); + data_arr.push_back(new mem_array); + } + } + + if (g_ip->force_cache_config && is_tag == false) + { + wr = wt_max; + iter = niter; + if(g_ip->nspd != 0) { + Nspd = MAXDATASPD; + } + if (g_ip->ndsam1 != 0) { + Ndsam_lev_1 = MAX_COL_MUX+1; + Ndsam_lev_2 = MAX_COL_MUX+1; + } + } + } + } + } + } + } + + delete data_arr.back(); + delete tag_arr.back(); + data_arr.pop_back(); + tag_arr.pop_back(); + + pthread_exit(NULL); +} + + + +bool calculate_time( + bool is_tag, + int pure_ram, + bool pure_cam, + double Nspd, + unsigned int Ndwl, + unsigned int Ndbl, + unsigned int Ndcm, + unsigned int Ndsam_lev_1, + unsigned int Ndsam_lev_2, + mem_array *ptr_array, + int flag_results_populate, + results_mem_array *ptr_results, + uca_org_t *ptr_fin_res, + Wire_type wt, // merge from cacti-7 to cacti3d + bool is_main_mem) +{ + DynamicParameter dyn_p(is_tag, pure_ram, pure_cam, Nspd, Ndwl, Ndbl, Ndcm, Ndsam_lev_1, Ndsam_lev_2, wt, is_main_mem); + + if (dyn_p.is_valid != true) + { + return false; + } + + UCA * uca = new UCA(dyn_p); + + + if (flag_results_populate) + { //For the final solution, populate the ptr_results data structure -- TODO: copy only necessary variables + } + else + { + int num_act_mats_hor_dir = uca->bank.dp.num_act_mats_hor_dir; + int num_mats = uca->bank.dp.num_mats; + bool is_fa = uca->bank.dp.fully_assoc; + bool pure_cam = uca->bank.dp.pure_cam; + ptr_array->Ndwl = Ndwl; + ptr_array->Ndbl = Ndbl; + ptr_array->Nspd = Nspd; + ptr_array->deg_bl_muxing = dyn_p.deg_bl_muxing; + ptr_array->Ndsam_lev_1 = Ndsam_lev_1; + ptr_array->Ndsam_lev_2 = Ndsam_lev_2; + ptr_array->access_time = uca->access_time; + ptr_array->cycle_time = uca->cycle_time; + ptr_array->multisubbank_interleave_cycle_time = uca->multisubbank_interleave_cycle_time; + ptr_array->area_ram_cells = uca->area_all_dataramcells; + ptr_array->area = uca->area.get_area(); + if(g_ip->is_3d_mem) + { //ptr_array->area = (uca->area_all_dataramcells)/0.5; + ptr_array->area = uca->area.get_area(); + if(g_ip->num_die_3d>1) + ptr_array->area += uca->area_TSV_tot; + } + + ptr_array->height = uca->area.h; + ptr_array->width = uca->area.w; + ptr_array->mat_height = uca->bank.mat.area.h; + ptr_array->mat_length = uca->bank.mat.area.w; + ptr_array->subarray_height = uca->bank.mat.subarray.area.h; + ptr_array->subarray_length = uca->bank.mat.subarray.area.w; + ptr_array->power = uca->power; + ptr_array->delay_senseamp_mux_decoder = + MAX(uca->delay_array_to_sa_mux_lev_1_decoder, + uca->delay_array_to_sa_mux_lev_2_decoder); + ptr_array->delay_before_subarray_output_driver = uca->delay_before_subarray_output_driver; + ptr_array->delay_from_subarray_output_driver_to_output = uca->delay_from_subarray_out_drv_to_out; + + ptr_array->delay_route_to_bank = uca->htree_in_add->delay; + ptr_array->delay_input_htree = uca->bank.htree_in_add->delay; + ptr_array->delay_row_predecode_driver_and_block = uca->bank.mat.r_predec->delay; + ptr_array->delay_row_decoder = uca->bank.mat.row_dec->delay; + ptr_array->delay_bitlines = uca->bank.mat.delay_bitline; + ptr_array->delay_matchlines = uca->bank.mat.delay_matchchline; + ptr_array->delay_sense_amp = uca->bank.mat.delay_sa; + ptr_array->delay_subarray_output_driver = uca->bank.mat.delay_subarray_out_drv_htree; + ptr_array->delay_dout_htree = uca->bank.htree_out_data->delay; + ptr_array->delay_comparator = uca->bank.mat.delay_comparator; + + if(g_ip->is_3d_mem) + { + ptr_array->delay_row_activate_net = uca->membus_RAS->delay_bus; + ptr_array->delay_row_predecode_driver_and_block = uca->membus_RAS->delay_add_predecoder; + ptr_array->delay_row_decoder = uca->membus_RAS->delay_add_decoder; + ptr_array->delay_local_wordline = uca->membus_RAS->delay_lwl_drv; + ptr_array->delay_column_access_net = uca->membus_CAS->delay_bus; + ptr_array->delay_column_predecoder = uca->membus_CAS->delay_add_predecoder; + ptr_array->delay_column_decoder = uca->membus_CAS->delay_add_decoder; + ptr_array->delay_column_selectline = 0; // Integrated into add_decoder + ptr_array->delay_datapath_net = uca->membus_data->delay_bus; + ptr_array->delay_global_data = uca->membus_data->delay_global_data; + ptr_array->delay_local_data_and_drv = uca->membus_data->delay_local_data; + ptr_array->delay_subarray_output_driver = uca->bank.mat.delay_subarray_out_drv; + ptr_array->delay_data_buffer = uca->membus_data->delay_data_buffer; + + /*ptr_array->energy_row_activate_net = uca->membus_RAS->add_bits * (uca->membus_RAS->center_stripe->power.readOp.dynamic + uca->membus_RAS->bank_bus->power.readOp.dynamic); + ptr_array->energy_row_predecode_driver_and_block = uca->membus_RAS->add_predec->power.readOp.dynamic; + ptr_array->energy_row_decoder = uca->membus_RAS->add_dec->power.readOp.dynamic; + ptr_array->energy_local_wordline = uca->membus_RAS->num_lwl_drv * uca->membus_RAS->lwl_drv->power.readOp.dynamic; + ptr_array->energy_column_access_net = uca->membus_CAS->add_bits * (uca->membus_CAS->center_stripe->power.readOp.dynamic + uca->membus_CAS->bank_bus->power.readOp.dynamic); + ptr_array->energy_column_predecoder = uca->membus_CAS->add_predec->power.readOp.dynamic; + ptr_array->energy_column_decoder = uca->membus_CAS->add_dec->power.readOp.dynamic; + ptr_array->energy_column_selectline = uca->membus_CAS->column_sel->power.readOp.dynamic; + ptr_array->energy_datapath_net = uca->membus_data->data_bits * (uca->membus_data->center_stripe->power.readOp.dynamic + uca->membus_data->bank_bus->power.readOp.dynamic); + ptr_array->energy_global_data = uca->membus_data->data_bits * (uca->membus_data->global_data->power.readOp.dynamic); + ptr_array->energy_local_data_and_drv = uca->membus_data->data_bits * (uca->membus_data->data_drv->power.readOp.dynamic); + ptr_array->energy_data_buffer = 0;*/ + + ptr_array->energy_row_activate_net = uca->membus_RAS->power_bus.readOp.dynamic; + ptr_array->energy_row_predecode_driver_and_block = uca->membus_RAS->power_add_predecoder.readOp.dynamic; + ptr_array->energy_row_decoder = uca->membus_RAS->power_add_decoders.readOp.dynamic; + ptr_array->energy_local_wordline = uca->membus_RAS->power_lwl_drv.readOp.dynamic; + ptr_array->energy_bitlines = dyn_p.Ndwl * uca->bank.mat.power_bitline.readOp.dynamic; + ptr_array->energy_sense_amp = dyn_p.Ndwl * uca->bank.mat.power_sa.readOp.dynamic; + + ptr_array->energy_column_access_net = uca->membus_CAS->power_bus.readOp.dynamic; + ptr_array->energy_column_predecoder = uca->membus_CAS->power_add_predecoder.readOp.dynamic; + ptr_array->energy_column_decoder = uca->membus_CAS->power_add_decoders.readOp.dynamic; + ptr_array->energy_column_selectline = uca->membus_CAS->power_col_sel.readOp.dynamic; + + ptr_array->energy_datapath_net = uca->membus_data->power_bus.readOp.dynamic; + ptr_array->energy_global_data = uca->membus_data->power_global_data.readOp.dynamic; + ptr_array->energy_local_data_and_drv = uca->membus_data->power_local_data.readOp.dynamic; + ptr_array->energy_subarray_output_driver = uca->bank.mat.power_subarray_out_drv.readOp.dynamic; // + ptr_array->energy_data_buffer = 0; + + ptr_array->area_lwl_drv = uca->area_lwl_drv; + ptr_array->area_row_predec_dec = uca->area_row_predec_dec; + ptr_array->area_col_predec_dec = uca->area_col_predec_dec; + ptr_array->area_subarray = uca->area_subarray; + ptr_array->area_bus = uca->area_bus; + ptr_array->area_address_bus = uca->area_address_bus; + ptr_array->area_data_bus = uca->area_data_bus; + ptr_array->area_data_drv = uca->area_data_drv; + ptr_array->area_IOSA = uca->area_IOSA; + ptr_array->area_sense_amp = uca->area_sense_amp; + + } + + ptr_array->all_banks_height = uca->area.h; + ptr_array->all_banks_width = uca->area.w; + //ptr_array->area_efficiency = uca->area_all_dataramcells * 100 / (uca->area.get_area()); + ptr_array->area_efficiency = uca->area_all_dataramcells * 100 / ptr_array->area; + + ptr_array->power_routing_to_bank = uca->power_routing_to_bank; + ptr_array->power_addr_input_htree = uca->bank.htree_in_add->power; + ptr_array->power_data_input_htree = uca->bank.htree_in_data->power; +// cout<<"power_data_input_htree"<bank.htree_in_data->power.readOp.leakage<power_data_output_htree = uca->bank.htree_out_data->power; +// cout<<"power_data_output_htree"<bank.htree_out_data->power.readOp.leakage<power_row_predecoder_drivers = uca->bank.mat.r_predec->driver_power; + ptr_array->power_row_predecoder_drivers.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_predecoder_drivers.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_predecoder_drivers.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_row_predecoder_blocks = uca->bank.mat.r_predec->block_power; + ptr_array->power_row_predecoder_blocks.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_predecoder_blocks.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_predecoder_blocks.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_row_decoders = uca->bank.mat.power_row_decoders; + ptr_array->power_row_decoders.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_decoders.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_row_decoders.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_bit_mux_predecoder_drivers = uca->bank.mat.b_mux_predec->driver_power; + ptr_array->power_bit_mux_predecoder_drivers.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_predecoder_drivers.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_predecoder_drivers.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_bit_mux_predecoder_blocks = uca->bank.mat.b_mux_predec->block_power; + ptr_array->power_bit_mux_predecoder_blocks.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_predecoder_blocks.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_predecoder_blocks.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_bit_mux_decoders = uca->bank.mat.power_bit_mux_decoders; + ptr_array->power_bit_mux_decoders.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_decoders.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bit_mux_decoders.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_1_predecoder_drivers = uca->bank.mat.sa_mux_lev_1_predec->driver_power; + ptr_array->power_senseamp_mux_lev_1_predecoder_drivers .readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_predecoder_drivers .writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_predecoder_drivers .searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_1_predecoder_blocks = uca->bank.mat.sa_mux_lev_1_predec->block_power; + ptr_array->power_senseamp_mux_lev_1_predecoder_blocks.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_predecoder_blocks.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_predecoder_blocks.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_1_decoders = uca->bank.mat.power_sa_mux_lev_1_decoders; + ptr_array->power_senseamp_mux_lev_1_decoders.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_decoders.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_1_decoders.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_2_predecoder_drivers = uca->bank.mat.sa_mux_lev_2_predec->driver_power; + ptr_array->power_senseamp_mux_lev_2_predecoder_drivers.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_predecoder_drivers.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_predecoder_drivers.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_2_predecoder_blocks = uca->bank.mat.sa_mux_lev_2_predec->block_power; + ptr_array->power_senseamp_mux_lev_2_predecoder_blocks.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_predecoder_blocks.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_predecoder_blocks.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_senseamp_mux_lev_2_decoders = uca->bank.mat.power_sa_mux_lev_2_decoders; + ptr_array->power_senseamp_mux_lev_2_decoders .readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_decoders .writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_senseamp_mux_lev_2_decoders .searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_bitlines = uca->bank.mat.power_bitline; + ptr_array->power_bitlines.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bitlines.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_bitlines.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_sense_amps = uca->bank.mat.power_sa; + ptr_array->power_sense_amps.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_sense_amps.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_sense_amps.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_prechg_eq_drivers = uca->bank.mat.power_bl_precharge_eq_drv; + ptr_array->power_prechg_eq_drivers.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_prechg_eq_drivers.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_prechg_eq_drivers.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_output_drivers_at_subarray = uca->bank.mat.power_subarray_out_drv; + ptr_array->power_output_drivers_at_subarray.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_output_drivers_at_subarray.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_output_drivers_at_subarray.searchOp.dynamic *= num_act_mats_hor_dir; + + ptr_array->power_comparators = uca->bank.mat.power_comparator; + ptr_array->power_comparators.readOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_comparators.writeOp.dynamic *= num_act_mats_hor_dir; + ptr_array->power_comparators.searchOp.dynamic *= num_act_mats_hor_dir; + +// cout << " num of mats: " << dyn_p.num_mats << endl; + if (is_fa || pure_cam) + { + ptr_array->power_htree_in_search = uca->bank.htree_in_search->power; +// cout<<"power_htree_in_search"<bank.htree_in_search->power.readOp.leakage<power_htree_out_search = uca->bank.htree_out_search->power; +// cout<<"power_htree_out_search"<bank.htree_out_search->power.readOp.leakage<power_searchline = uca->bank.mat.power_searchline; +// cout<<"power_searchlineh"<bank.mat.power_searchline.readOp.leakage<power_searchline.searchOp.dynamic *= num_mats; + ptr_array->power_searchline_precharge = uca->bank.mat.power_searchline_precharge; + ptr_array->power_searchline_precharge.searchOp.dynamic *= num_mats; + ptr_array->power_matchlines = uca->bank.mat.power_matchline; + ptr_array->power_matchlines.searchOp.dynamic *= num_mats; + ptr_array->power_matchline_precharge = uca->bank.mat.power_matchline_precharge; + ptr_array->power_matchline_precharge.searchOp.dynamic *= num_mats; + ptr_array->power_matchline_to_wordline_drv = uca->bank.mat.power_ml_to_ram_wl_drv; +// cout<<"power_matchline.searchOp.leakage"<bank.mat.power_matchline.searchOp.leakage<activate_energy = uca->activate_energy; + ptr_array->read_energy = uca->read_energy; + ptr_array->write_energy = uca->write_energy; + ptr_array->precharge_energy = uca->precharge_energy; + ptr_array->refresh_power = uca->refresh_power; + ptr_array->leak_power_subbank_closed_page = uca->leak_power_subbank_closed_page; + ptr_array->leak_power_subbank_open_page = uca->leak_power_subbank_open_page; + ptr_array->leak_power_request_and_reply_networks = uca->leak_power_request_and_reply_networks; + + ptr_array->precharge_delay = uca->precharge_delay; + + if(g_ip->is_3d_mem) + { + //CACTI3DD + ptr_array->t_RCD = uca->t_RCD; + ptr_array->t_RAS = uca->t_RAS; + ptr_array->t_RC = uca->t_RC; + ptr_array->t_CAS = uca->t_CAS; + ptr_array->t_RP = uca->t_RP; + ptr_array->t_RRD = uca->t_RRD; + + ptr_array->activate_energy = uca->activate_energy; + ptr_array->read_energy = uca->read_energy; + ptr_array->write_energy = uca->write_energy; + ptr_array->precharge_energy = uca->precharge_energy; + + + ptr_array->activate_power = uca->activate_power; + ptr_array->read_power = uca->read_power; + ptr_array->write_power = uca->write_power; + ptr_array->peak_read_power = uca->read_energy/((g_ip->burst_depth)/(g_ip->sys_freq_MHz*1e6)/2); + + ptr_array->num_row_subarray = dyn_p.num_r_subarray; + ptr_array->num_col_subarray = dyn_p.num_c_subarray; + + + ptr_array->delay_TSV_tot = uca->delay_TSV_tot; + ptr_array->area_TSV_tot = uca->area_TSV_tot; + ptr_array->dyn_pow_TSV_tot = uca->dyn_pow_TSV_tot; + ptr_array->dyn_pow_TSV_per_access = uca->dyn_pow_TSV_per_access; + ptr_array->num_TSV_tot = uca->num_TSV_tot; + + //Covers the previous values + //ptr_array->area = g_ip->num_die_3d * (uca->area_per_bank * g_ip->nbanks); + //ptr_array->area_efficiency = g_ip->num_die_3d * uca->area_all_dataramcells * 100 / ptr_array->area; + } +// cout<<"power_matchline.searchOp.leakage"<bank.mat.<bank.mat.subarray.get_total_cell_area()<power_gating) + { + ptr_array->sram_sleep_tx_width= uca->bank.mat.sram_sleep_tx->width; + ptr_array->sram_sleep_tx_area= uca->bank.mat.array_sleep_tx_area; + ptr_array->sram_sleep_wakeup_latency= uca->bank.mat.array_wakeup_t; + ptr_array->sram_sleep_wakeup_energy= uca->bank.mat.array_wakeup_e.readOp.dynamic; + + ptr_array->wl_sleep_tx_width= uca->bank.mat.row_dec->sleeptx->width; + ptr_array->wl_sleep_tx_area= uca->bank.mat.wl_sleep_tx_area; + ptr_array->wl_sleep_wakeup_latency= uca->bank.mat.wl_wakeup_t; + ptr_array->wl_sleep_wakeup_energy= uca->bank.mat.wl_wakeup_e.readOp.dynamic; + + ptr_array->bl_floating_wakeup_latency= uca->bank.mat.blfloating_wakeup_t; + ptr_array->bl_floating_wakeup_energy= uca->bank.mat.blfloating_wakeup_e.readOp.dynamic; + + ptr_array->array_leakage= uca->bank.array_leakage; + ptr_array->wl_leakage= uca->bank.wl_leakage; + ptr_array->cl_leakage= uca->bank.cl_leakage; + } + + ptr_array->num_active_mats = uca->bank.dp.num_act_mats_hor_dir; + ptr_array->num_submarray_mats = uca->bank.mat.num_subarrays_per_mat; + // cout<<"array_leakage"<array_leakage<wl_leakage<cl_leakage<min_delay)*100/minval->min_delay) > g_ip->delay_dev) { + return false; + } + if (((u.power.readOp.dynamic - minval->min_dyn)/minval->min_dyn)*100 > + g_ip->dynamic_power_dev) { + return false; + } + if (((u.power.readOp.leakage - minval->min_leakage)/minval->min_leakage)*100 > + g_ip->leakage_power_dev) { + return false; + } + if (((u.cycle_time - minval->min_cyc)/minval->min_cyc)*100 > + g_ip->cycle_time_dev) { + return false; + } + if (((u.area - minval->min_area)/minval->min_area)*100 > + g_ip->area_dev) { + return false; + } + return true; +} + +bool check_mem_org(mem_array & u, const min_values_t *minval) +{ + if (((u.access_time - minval->min_delay)*100/minval->min_delay) > g_ip->delay_dev) { + return false; + } + if (((u.power.readOp.dynamic - minval->min_dyn)/minval->min_dyn)*100 > + g_ip->dynamic_power_dev) { + return false; + } + if (((u.power.readOp.leakage - minval->min_leakage)/minval->min_leakage)*100 > + g_ip->leakage_power_dev) { + return false; + } + if (((u.cycle_time - minval->min_cyc)/minval->min_cyc)*100 > + g_ip->cycle_time_dev) { + return false; + } + if (((u.area - minval->min_area)/minval->min_area)*100 > + g_ip->area_dev) { + return false; + } + return true; +} + + + + +void find_optimal_uca(uca_org_t *res, min_values_t * minval, list & ulist) +{ + double cost = 0; + double min_cost = BIGNUM; + float d, a, dp, lp, c; + + dp = g_ip->dynamic_power_wt; + lp = g_ip->leakage_power_wt; + a = g_ip->area_wt; + d = g_ip->delay_wt; + c = g_ip->cycle_time_wt; + + if (ulist.empty() == true) + { + cout << "ERROR: no valid cache organizations found" << endl; + exit(0); + } + + for (list::iterator niter = ulist.begin(); niter != ulist.end(); niter++) + { + if (g_ip->ed == 1) + { + cost = ((niter)->access_time/minval->min_delay) * ((niter)->power.readOp.dynamic/minval->min_dyn); + if (min_cost > cost) + { + min_cost = cost; + *res = (*(niter)); + } + } + else if (g_ip->ed == 2) + { + cost = ((niter)->access_time/minval->min_delay)* + ((niter)->access_time/minval->min_delay)* + ((niter)->power.readOp.dynamic/minval->min_dyn); + if (min_cost > cost) + { + min_cost = cost; + *res = (*(niter)); + } + } + else + { + /* + * check whether the current organization + * meets the input deviation constraints + */ + bool v = check_uca_org(*niter, minval); + //if (minval->min_leakage == 0) minval->min_leakage = 0.1; //FIXME remove this after leakage modeling + + if (v) + { + cost = (d * ((niter)->access_time/minval->min_delay) + + c * ((niter)->cycle_time/minval->min_cyc) + + dp * ((niter)->power.readOp.dynamic/minval->min_dyn) + + lp * ((niter)->power.readOp.leakage/minval->min_leakage) + + a * ((niter)->area/minval->min_area)); + //fprintf(stderr, "cost = %g\n", cost); + + if (min_cost > cost) { + min_cost = cost; + *res = (*(niter)); + niter = ulist.erase(niter); + if (niter!=ulist.begin()) + niter--; + } + } + else { + niter = ulist.erase(niter); + if (niter!=ulist.begin()) + niter--; + } + } + } + + if (min_cost == BIGNUM) + { + cout << "ERROR: no cache organizations met optimization criteria" << endl; + exit(0); + } +} + + + +void filter_tag_arr(const min_values_t * min, list & list) +{ + double cost = BIGNUM; + double cur_cost; + double wt_delay = g_ip->delay_wt, wt_dyn = g_ip->dynamic_power_wt, wt_leakage = g_ip->leakage_power_wt, wt_cyc = g_ip->cycle_time_wt, wt_area = g_ip->area_wt; + mem_array * res = NULL; + + if (list.empty() == true) + { + cout << "ERROR: no valid tag organizations found" << endl; + exit(1); + } + + + while (list.empty() != true) + { + bool v = check_mem_org(*list.back(), min); + if (v) + { + cur_cost = wt_delay * (list.back()->access_time/min->min_delay) + + wt_dyn * (list.back()->power.readOp.dynamic/min->min_dyn) + + wt_leakage * (list.back()->power.readOp.leakage/min->min_leakage) + + wt_area * (list.back()->area/min->min_area) + + wt_cyc * (list.back()->cycle_time/min->min_cyc); + } + else + { + cur_cost = BIGNUM; + } + if (cur_cost < cost) + { + if (res != NULL) + { + delete res; + } + cost = cur_cost; + res = list.back(); + } + else + { + delete list.back(); + } + list.pop_back(); + } + if(!res) + { + cout << "ERROR: no valid tag organizations found" << endl; + exit(0); + } + + list.push_back(res); +} + + + +void filter_data_arr(list & curr_list) +{ + if (curr_list.empty() == true) + { + cout << "ERROR: no valid data array organizations found" << endl; + exit(1); + } + + list::iterator iter; + + for (iter = curr_list.begin(); iter != curr_list.end(); ++iter) + { + mem_array * m = *iter; + + if (m == NULL) exit(1); + + if(((m->access_time - m->arr_min->min_delay)/m->arr_min->min_delay > 0.5) && + ((m->power.readOp.dynamic - m->arr_min->min_dyn)/m->arr_min->min_dyn > 0.5)) + { + delete m; + iter = curr_list.erase(iter); + iter --; + } + } +} + + + +/* + * Performs exhaustive search across different sub-array sizes, + * wire types and aspect ratios to find an optimal UCA organization + * 1. First different valid tag array organizations are calculated + * and stored in tag_arr array + * 2. The exhaustive search is repeated to find valid data array + * organizations and stored in data_arr array + * 3. Cache area, delay, power, and cycle time for different + * cache organizations are calculated based on the + * above results + * 4. Cache model with least cost is picked from sol_list + */ +void solve(uca_org_t *fin_res) +{ + ///bool is_dram = false; + int pure_ram = g_ip->pure_ram; + bool pure_cam = g_ip->pure_cam; + + init_tech_params(g_ip->F_sz_um, false); + g_ip->print_detail_debug = 0; // ---detail outputs for debug, initiated for 3D memory + + list tag_arr (0); + list data_arr(0); + list::iterator miter; + list sol_list(1, uca_org_t()); + + fin_res->tag_array.access_time = 0; + fin_res->tag_array.Ndwl = 0; + fin_res->tag_array.Ndbl = 0; + fin_res->tag_array.Nspd = 0; + fin_res->tag_array.deg_bl_muxing = 0; + fin_res->tag_array.Ndsam_lev_1 = 0; + fin_res->tag_array.Ndsam_lev_2 = 0; + + + // distribute calculate_time() execution to multiple threads + calc_time_mt_wrapper_struct * calc_array = new calc_time_mt_wrapper_struct[nthreads]; + pthread_t threads[nthreads]; + + for (uint32_t t = 0; t < nthreads; t++) + { + calc_array[t].tid = t; + calc_array[t].pure_ram = pure_ram; + calc_array[t].pure_cam = pure_cam; + calc_array[t].data_res = new min_values_t(); + calc_array[t].tag_res = new min_values_t(); + } + + bool is_tag; + ///uint32_t ram_cell_tech_type; + + // If it's a cache, first calculate the area, delay and power for all tag array partitions. + if (!(pure_ram||pure_cam||g_ip->fully_assoc)) + { //cache + is_tag = true; + /// ram_cell_tech_type = g_ip->tag_arr_ram_cell_tech_type; + /// is_dram = ((ram_cell_tech_type == lp_dram) || (ram_cell_tech_type == comm_dram)); + init_tech_params(g_ip->F_sz_um, is_tag); + + for (uint32_t t = 0; t < nthreads; t++) + { + calc_array[t].is_tag = is_tag; + calc_array[t].is_main_mem = false; + calc_array[t].Nspd_min = 0.125; + pthread_create(&threads[t], NULL, calc_time_mt_wrapper, (void *)(&(calc_array[t]))); + } + + for (uint32_t t = 0; t < nthreads; t++) + { + pthread_join(threads[t], NULL); + } + + for (uint32_t t = 0; t < nthreads; t++) + { + calc_array[t].data_arr.sort(mem_array::lt); + data_arr.merge(calc_array[t].data_arr, mem_array::lt); + calc_array[t].tag_arr.sort(mem_array::lt); + tag_arr.merge(calc_array[t].tag_arr, mem_array::lt); + } + } + + + // calculate the area, delay and power for all data array partitions (for cache or plain RAM). +// if (!g_ip->fully_assoc) +// {//in the new cacti, cam, fully_associative cache are processed as single array in the data portion + is_tag = false; + /// ram_cell_tech_type = g_ip->data_arr_ram_cell_tech_type; + /// is_dram = ((ram_cell_tech_type == lp_dram) || (ram_cell_tech_type == comm_dram)); + init_tech_params(g_ip->F_sz_um, is_tag); + + for (uint32_t t = 0; t < nthreads; t++) + { + calc_array[t].is_tag = is_tag; + calc_array[t].is_main_mem = g_ip->is_main_mem; + if (!(pure_cam||g_ip->fully_assoc)) + { + calc_array[t].Nspd_min = (double)(g_ip->out_w)/(double)(g_ip->block_sz*8); + } + else + { + calc_array[t].Nspd_min = 1; + } + + pthread_create(&threads[t], NULL, calc_time_mt_wrapper, (void *)(&(calc_array[t]))); + } + + for (uint32_t t = 0; t < nthreads; t++) + { + pthread_join(threads[t], NULL); + } + + data_arr.clear(); + for (uint32_t t = 0; t < nthreads; t++) + { + calc_array[t].data_arr.sort(mem_array::lt); + data_arr.merge(calc_array[t].data_arr, mem_array::lt); + } +// } + + + min_values_t * d_min = new min_values_t(); + min_values_t * t_min = new min_values_t(); + min_values_t * cache_min = new min_values_t(); + + for (uint32_t t = 0; t < nthreads; t++) + { + d_min->update_min_values(calc_array[t].data_res); + t_min->update_min_values(calc_array[t].tag_res); + } + + for (miter = data_arr.begin(); miter != data_arr.end(); miter++) + { + (*miter)->arr_min = d_min; + } + + + //cout << data_arr.size() << "\t" << tag_arr.size() <<" before\n"; + filter_data_arr(data_arr); + if(!(pure_ram||pure_cam||g_ip->fully_assoc)) + { + filter_tag_arr(t_min, tag_arr); + } + //cout << data_arr.size() << "\t" << tag_arr.size() <<" after\n"; + + + if (pure_ram||pure_cam||g_ip->fully_assoc) + { + for (miter = data_arr.begin(); miter != data_arr.end(); miter++) + { + uca_org_t & curr_org = sol_list.back(); + curr_org.tag_array2 = NULL; + curr_org.data_array2 = (*miter); + + curr_org.find_delay(); + curr_org.find_energy(); + curr_org.find_area(); + curr_org.find_cyc(); + + //update min values for the entire cache + cache_min->update_min_values(curr_org); + + sol_list.push_back(uca_org_t()); + } + } + else + { + while (tag_arr.empty() != true) + { + mem_array * arr_temp = (tag_arr.back()); + //delete tag_arr.back(); + tag_arr.pop_back(); + + for (miter = data_arr.begin(); miter != data_arr.end(); miter++) + { + uca_org_t & curr_org = sol_list.back(); + curr_org.tag_array2 = arr_temp; + curr_org.data_array2 = (*miter); + + curr_org.find_delay(); + curr_org.find_energy(); + curr_org.find_area(); + curr_org.find_cyc(); + + //update min values for the entire cache + cache_min->update_min_values(curr_org); + + sol_list.push_back(uca_org_t()); + } + } + } + + sol_list.pop_back(); + + find_optimal_uca(fin_res, cache_min, sol_list); + + sol_list.clear(); + + for (miter = data_arr.begin(); miter != data_arr.end(); ++miter) + { + if (*miter != fin_res->data_array2) + { + delete *miter; + } + } + data_arr.clear(); + + for (uint32_t t = 0; t < nthreads; t++) + { + delete calc_array[t].data_res; + delete calc_array[t].tag_res; + } + + delete [] calc_array; + delete cache_min; + delete d_min; + delete t_min; +} + +void update(uca_org_t *fin_res) +{ + if(fin_res->tag_array2) + { + init_tech_params(g_ip->F_sz_um,true); + DynamicParameter tag_arr_dyn_p(true, g_ip->pure_ram, g_ip->pure_cam, fin_res->tag_array2->Nspd, fin_res->tag_array2->Ndwl, fin_res->tag_array2->Ndbl, fin_res->tag_array2->Ndcm, fin_res->tag_array2->Ndsam_lev_1, fin_res->tag_array2->Ndsam_lev_2, fin_res->data_array2->wt, g_ip->is_main_mem); + if(tag_arr_dyn_p.is_valid) + { + UCA * tag_arr = new UCA(tag_arr_dyn_p); + fin_res->tag_array2->power = tag_arr->power; + } + else + { + cout << "ERROR: Cannot retrieve array structure for leakage feedback" << endl; + exit(1); + } + } + init_tech_params(g_ip->F_sz_um,false); + DynamicParameter data_arr_dyn_p(false, g_ip->pure_ram, g_ip->pure_cam, fin_res->data_array2->Nspd, fin_res->data_array2->Ndwl, fin_res->data_array2->Ndbl, fin_res->data_array2->Ndcm, fin_res->data_array2->Ndsam_lev_1, fin_res->data_array2->Ndsam_lev_2, fin_res->data_array2->wt, g_ip->is_main_mem); + if(data_arr_dyn_p.is_valid) + { + UCA * data_arr = new UCA(data_arr_dyn_p); + fin_res->data_array2->power = data_arr->power; + } + else + { + cout << "ERROR: Cannot retrieve array structure for leakage feedback" << endl; + exit(1); + } + + fin_res->find_energy(); +} + diff --git a/Project_FARSI/cacti_for_FARSI/Ucache.h b/Project_FARSI/cacti_for_FARSI/Ucache.h new file mode 100644 index 00000000..bfa1a308 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/Ucache.h @@ -0,0 +1,118 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + +#ifndef __UCACHE_H__ +#define __UCACHE_H__ + +#include +#include "area.h" +#include "router.h" +#include "nuca.h" + + +class min_values_t +{ + public: + double min_delay; + double min_dyn; + double min_leakage; + double min_area; + double min_cyc; + + min_values_t() : min_delay(BIGNUM), min_dyn(BIGNUM), min_leakage(BIGNUM), min_area(BIGNUM), min_cyc(BIGNUM) { } + + void update_min_values(const min_values_t * val); + void update_min_values(const uca_org_t & res); + void update_min_values(const nuca_org_t * res); + void update_min_values(const mem_array * res); +}; + + + +struct solution +{ + int tag_array_index; + int data_array_index; + list::iterator tag_array_iter; + list::iterator data_array_iter; + double access_time; + double cycle_time; + double area; + double efficiency; + powerDef total_power; +}; + + + +bool calculate_time( + bool is_tag, + int pure_ram, + bool pure_cam, + double Nspd, + unsigned int Ndwl, + unsigned int Ndbl, + unsigned int Ndcm, + unsigned int Ndsam_lev_1, + unsigned int Ndsam_lev_2, + mem_array *ptr_array, + int flag_results_populate, + results_mem_array *ptr_results, + uca_org_t *ptr_fin_res, + Wire_type wtype, // merge from cacti-7 to cacti3d + bool is_main_mem); +void update(uca_org_t *fin_res); + +void solve(uca_org_t *fin_res); +void init_tech_params(double tech, bool is_tag); + + +struct calc_time_mt_wrapper_struct +{ + uint32_t tid; + bool is_tag; + bool pure_ram; + bool pure_cam; + bool is_main_mem; + double Nspd_min; + + min_values_t * data_res; + min_values_t * tag_res; + + list data_arr; + list tag_arr; +}; + +void *calc_time_mt_wrapper(void * void_obj); + +void print_g_tp(); + +#endif diff --git a/Project_FARSI/cacti_for_FARSI/arbiter.cc b/Project_FARSI/cacti_for_FARSI/arbiter.cc new file mode 100644 index 00000000..f09dcb78 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/arbiter.cc @@ -0,0 +1,130 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + +#include "arbiter.h" + +Arbiter::Arbiter( + double n_req, + double flit_size_, + double output_len, + /*TechnologyParameter::*/DeviceType *dt + ):R(n_req), flit_size(flit_size_), + o_len (output_len), deviceType(dt) +{ + min_w_pmos = deviceType->n_to_p_eff_curr_drv_ratio*g_tp.min_w_nmos_; + Vdd = dt->Vdd; + double technology = g_ip->F_sz_um; + NTn1 = 13.5*technology/2; + PTn1 = 76*technology/2; + NTn2 = 13.5*technology/2; + PTn2 = 76*technology/2; + NTi = 12.5*technology/2; + PTi = 25*technology/2; + NTtr = 10*technology/2; /*Transmission gate's nmos tr. length*/ + PTtr = 20*technology/2; /* pmos tr. length*/ +} + +Arbiter::~Arbiter(){} + +double +Arbiter::arb_req() { + double temp = ((R-1)*(2*gate_C(NTn1, 0)+gate_C(PTn1, 0)) + 2*gate_C(NTn2, 0) + + gate_C(PTn2, 0) + gate_C(NTi, 0) + gate_C(PTi, 0) + + drain_C_(NTi, 0, 1, 1, g_tp.cell_h_def) + drain_C_(PTi, 1, 1, 1, g_tp.cell_h_def)); + return temp; +} + +double +Arbiter::arb_pri() { + double temp = 2*(2*gate_C(NTn1, 0)+gate_C(PTn1, 0)); /* switching capacitance + of flip-flop is ignored */ + return temp; +} + + +double +Arbiter::arb_grant() { + double temp = drain_C_(NTn1, 0, 1, 1, g_tp.cell_h_def)*2 + drain_C_(PTn1, 1, 1, 1, g_tp.cell_h_def) + crossbar_ctrline(); + return temp; +} + +double +Arbiter::arb_int() { + double temp = (drain_C_(NTn1, 0, 1, 1, g_tp.cell_h_def)*2 + drain_C_(PTn1, 1, 1, 1, g_tp.cell_h_def) + + 2*gate_C(NTn2, 0) + gate_C(PTn2, 0)); + return temp; +} + +void +Arbiter::compute_power() { + power.readOp.dynamic = (R*arb_req()*Vdd*Vdd/2 + R*arb_pri()*Vdd*Vdd/2 + + arb_grant()*Vdd*Vdd + arb_int()*0.5*Vdd*Vdd); + double nor1_leak = cmos_Isub_leakage(g_tp.min_w_nmos_*NTn1*2, min_w_pmos * PTn1*2, 2, nor); + double nor2_leak = cmos_Isub_leakage(g_tp.min_w_nmos_*NTn2*R, min_w_pmos * PTn2*R, 2, nor); + double not_leak = cmos_Isub_leakage(g_tp.min_w_nmos_*NTi, min_w_pmos * PTi, 1, inv); + double nor1_leak_gate = cmos_Ig_leakage(g_tp.min_w_nmos_*NTn1*2, min_w_pmos * PTn1*2, 2, nor); + double nor2_leak_gate = cmos_Ig_leakage(g_tp.min_w_nmos_*NTn2*R, min_w_pmos * PTn2*R, 2, nor); + double not_leak_gate = cmos_Ig_leakage(g_tp.min_w_nmos_*NTi, min_w_pmos * PTi, 1, inv); + power.readOp.leakage = (nor1_leak + nor2_leak + not_leak)*Vdd; //FIXME include priority table leakage + power.readOp.gate_leakage = nor1_leak_gate*Vdd + nor2_leak_gate*Vdd + not_leak_gate*Vdd; +} + +double //wire cap with triple spacing +Arbiter::Cw3(double length) { + Wire wc(g_ip->wt, length, 1, 3, 3); + double temp = (wc.wire_cap(length,true)); + return temp; +} + +double +Arbiter::crossbar_ctrline() { + double temp = (Cw3(o_len * 1e-6 /* m */) + + drain_C_(NTi, 0, 1, 1, g_tp.cell_h_def) + drain_C_(PTi, 1, 1, 1, g_tp.cell_h_def) + + gate_C(NTi, 0) + gate_C(PTi, 0)); + return temp; +} + +double +Arbiter::transmission_buf_ctrcap() { + double temp = gate_C(NTtr, 0)+gate_C(PTtr, 0); + return temp; +} + + +void Arbiter::print_arbiter() +{ + cout << "\nArbiter Stats (" << R << " input arbiter" << ")\n\n"; + cout << "Flit size : " << flit_size << " bits" << endl; + cout << "Dynamic Power : " << power.readOp.dynamic*1e9 << " (nJ)" << endl; + cout << "Leakage Power : " << power.readOp.leakage*1e3 << " (mW)" << endl; +} + + diff --git a/Project_FARSI/cacti_for_FARSI/arbiter.h b/Project_FARSI/cacti_for_FARSI/arbiter.h new file mode 100644 index 00000000..8358e957 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/arbiter.h @@ -0,0 +1,77 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + +#ifndef __ARBITER__ +#define __ARBITER__ + +#include +#include +#include "basic_circuit.h" +#include "cacti_interface.h" +#include "component.h" +#include "parameter.h" +#include "mat.h" +#include "wire.h" + +class Arbiter : public Component +{ + public: + Arbiter( + double Req, + double flit_sz, + double output_len, + /*TechnologyParameter::*/DeviceType *dt = &(g_tp.peri_global)); + ~Arbiter(); + + void print_arbiter(); + double arb_req(); + double arb_pri(); + double arb_grant(); + double arb_int(); + void compute_power(); + double Cw3(double len); + double crossbar_ctrline(); + double transmission_buf_ctrcap(); + + + + private: + double NTn1, PTn1, NTn2, PTn2, R, PTi, NTi; + double flit_size; + double NTtr, PTtr; + double o_len; + /*TechnologyParameter::*/DeviceType *deviceType; + double TriS1, TriS2; + double min_w_pmos, Vdd; + +}; + +#endif diff --git a/Project_FARSI/cacti_for_FARSI/area.cc b/Project_FARSI/cacti_for_FARSI/area.cc new file mode 100644 index 00000000..d6a37468 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/area.cc @@ -0,0 +1,46 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + + +#include "area.h" +#include "component.h" +#include "decoder.h" +#include "parameter.h" +#include "basic_circuit.h" +#include +#include +#include + +using namespace std; + + + diff --git a/Project_FARSI/cacti_for_FARSI/area.h b/Project_FARSI/cacti_for_FARSI/area.h new file mode 100644 index 00000000..a592dbcc --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/area.h @@ -0,0 +1,71 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + + +#ifndef __AREA_H__ +#define __AREA_H__ + +#include "cacti_interface.h" +#include "basic_circuit.h" + +using namespace std; + +class Area +{ + public: + double w; + double h; + + Area():w(0), h(0), area(0) { } + double get_w() const { return w; } + double get_h() const { return h; } + double get_area() const + { + if (w == 0 && h == 0) + { + return area; + } + else + { + return w*h; + } + } + void set_w(double w_) { w = w_; } + void set_h(double h_) { h = h_; } + void set_area(double a_) { area = a_; } + + private: + double area; +}; + +#endif + diff --git a/Project_FARSI/cacti_for_FARSI/bank.cc b/Project_FARSI/cacti_for_FARSI/bank.cc new file mode 100644 index 00000000..e7e5d819 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/bank.cc @@ -0,0 +1,206 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + + +#include "bank.h" +#include + + +Bank::Bank(const DynamicParameter & dyn_p): + dp(dyn_p), mat(dp), + num_addr_b_mat(dyn_p.number_addr_bits_mat), + num_mats_hor_dir(dyn_p.num_mats_h_dir), num_mats_ver_dir(dyn_p.num_mats_v_dir), + array_leakage(0), + wl_leakage(0), + cl_leakage(0) +{ +// Mat temp(dyn_p); + int RWP; + int ERP; + int EWP; + int SCHP; + + if (dp.use_inp_params) + { + RWP = dp.num_rw_ports; + ERP = dp.num_rd_ports; + EWP = dp.num_wr_ports; + SCHP = dp.num_search_ports; + } + else + { + RWP = g_ip->num_rw_ports; + ERP = g_ip->num_rd_ports; + EWP = g_ip->num_wr_ports; + SCHP = g_ip->num_search_ports; + } + + int total_addrbits = (dp.number_addr_bits_mat + dp.number_subbanks_decode)*(RWP+ERP+EWP); + int datainbits = dp.num_di_b_bank_per_port * (RWP + EWP); + int dataoutbits = dp.num_do_b_bank_per_port * (RWP + ERP); + int searchinbits; + int searchoutbits; + + if (dp.fully_assoc || dp.pure_cam) + { + datainbits = dp.num_di_b_bank_per_port * (RWP + EWP); + dataoutbits = dp.num_do_b_bank_per_port * (RWP + ERP); + searchinbits = dp.num_si_b_bank_per_port * SCHP; + searchoutbits = dp.num_so_b_bank_per_port * SCHP; + } + + if (!(dp.fully_assoc || dp.pure_cam)) + { + if (g_ip->fast_access && dp.is_tag == false) + { + dataoutbits *= g_ip->data_assoc; + } + + htree_in_add = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits, 0,dataoutbits,0, num_mats_ver_dir*2, num_mats_hor_dir*2, Add_htree); + htree_in_data = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits, 0,dataoutbits,0, num_mats_ver_dir*2, num_mats_hor_dir*2, Data_in_htree); + htree_out_data = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits, 0,dataoutbits,0, num_mats_ver_dir*2, num_mats_hor_dir*2, Data_out_htree); + +// htree_out_data = new Htree2 (g_ip->wt,(double) 100, (double)100, +// total_addrbits, datainbits, 0,dataoutbits,0, num_mats_ver_dir*2, num_mats_hor_dir*2, Data_out_htree); + + area.w = htree_in_data->area.w; + area.h = htree_in_data->area.h; + } + else + { + htree_in_add = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits, searchinbits,dataoutbits,searchoutbits, num_mats_ver_dir*2, num_mats_hor_dir*2, Add_htree); + htree_in_data = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits,searchinbits, dataoutbits, searchoutbits, num_mats_ver_dir*2, num_mats_hor_dir*2, Data_in_htree); + htree_out_data = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits,searchinbits, dataoutbits, searchoutbits,num_mats_ver_dir*2, num_mats_hor_dir*2, Data_out_htree); + htree_in_search = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits,searchinbits, dataoutbits, searchoutbits, num_mats_ver_dir*2, num_mats_hor_dir*2, Data_in_htree,true, true); + htree_out_search = new Htree2 (dp.wtype/*g_ip->wt*/,(double) mat.area.w, (double)mat.area.h, + total_addrbits, datainbits,searchinbits, dataoutbits, searchoutbits,num_mats_ver_dir*2, num_mats_hor_dir*2, Data_out_htree,true); + + area.w = htree_in_data->area.w; + area.h = htree_in_data->area.h; + } + + num_addr_b_row_dec = _log2(mat.subarray.num_rows); + num_addr_b_routed_to_mat_for_act = num_addr_b_row_dec; + num_addr_b_routed_to_mat_for_rd_or_wr = num_addr_b_mat - num_addr_b_row_dec; +} + + + +Bank::~Bank() +{ + delete htree_in_add; + delete htree_out_data; + delete htree_in_data; + if (dp.fully_assoc || dp.pure_cam) + { + delete htree_in_search; + delete htree_out_search; + } +} + + + +double Bank::compute_delays(double inrisetime) +{ + return mat.compute_delays(inrisetime); +} + + + +void Bank::compute_power_energy() +{ + mat.compute_power_energy(); + + if (!(dp.fully_assoc || dp.pure_cam)) + { + power.readOp.dynamic += mat.power.readOp.dynamic * dp.num_act_mats_hor_dir; + power.readOp.leakage += mat.power.readOp.leakage * dp.num_mats; + power.readOp.gate_leakage += mat.power.readOp.gate_leakage * dp.num_mats; + + power.readOp.dynamic += htree_in_add->power.readOp.dynamic; + power.readOp.dynamic += htree_out_data->power.readOp.dynamic; + + array_leakage += mat.array_leakage*dp.num_mats; + wl_leakage += mat.wl_leakage*dp.num_mats; + cl_leakage += mat.cl_leakage*dp.num_mats; +// +// power.readOp.leakage += htree_in_add->power.readOp.leakage; +// power.readOp.leakage += htree_in_data->power.readOp.leakage; +// power.readOp.leakage += htree_out_data->power.readOp.leakage; +// power.readOp.gate_leakage += htree_in_add->power.readOp.gate_leakage; +// power.readOp.gate_leakage += htree_in_data->power.readOp.gate_leakage; +// power.readOp.gate_leakage += htree_out_data->power.readOp.gate_leakage; + } + else + { + + power.readOp.dynamic += mat.power.readOp.dynamic ;//for fa and cam num_act_mats_hor_dir is 1 for plain r/w + power.readOp.leakage += mat.power.readOp.leakage * dp.num_mats; + power.readOp.gate_leakage += mat.power.readOp.gate_leakage * dp.num_mats; + + power.searchOp.dynamic += mat.power.searchOp.dynamic * dp.num_mats; + power.searchOp.dynamic += mat.power_bl_precharge_eq_drv.searchOp.dynamic + + mat.power_sa.searchOp.dynamic + + mat.power_bitline.searchOp.dynamic + + mat.power_subarray_out_drv.searchOp.dynamic+ + mat.ml_to_ram_wl_drv->power.readOp.dynamic; + + power.readOp.dynamic += htree_in_add->power.readOp.dynamic; + power.readOp.dynamic += htree_out_data->power.readOp.dynamic; + + power.searchOp.dynamic += htree_in_search->power.searchOp.dynamic; + power.searchOp.dynamic += htree_out_search->power.searchOp.dynamic; + + power.readOp.leakage += htree_in_add->power.readOp.leakage; + power.readOp.leakage += htree_in_data->power.readOp.leakage; + power.readOp.leakage += htree_out_data->power.readOp.leakage; + power.readOp.leakage += htree_in_search->power.readOp.leakage; + power.readOp.leakage += htree_out_search->power.readOp.leakage; + + + power.readOp.gate_leakage += htree_in_add->power.readOp.gate_leakage; + power.readOp.gate_leakage += htree_in_data->power.readOp.gate_leakage; + power.readOp.gate_leakage += htree_out_data->power.readOp.gate_leakage; + power.readOp.gate_leakage += htree_in_search->power.readOp.gate_leakage; + power.readOp.gate_leakage += htree_out_search->power.readOp.gate_leakage; + + } + +} + diff --git a/Project_FARSI/cacti_for_FARSI/bank.h b/Project_FARSI/cacti_for_FARSI/bank.h new file mode 100644 index 00000000..e12665f7 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/bank.h @@ -0,0 +1,74 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + + +#ifndef __BANK_H__ +#define __BANK_H__ + +#include "component.h" +#include "decoder.h" +#include "mat.h" +#include "htree2.h" + + +class Bank : public Component +{ + public: + Bank(const DynamicParameter & dyn_p); + ~Bank(); + double compute_delays(double inrisetime); // return outrisetime + void compute_power_energy(); + + const DynamicParameter & dp; + Mat mat; + Htree2 *htree_in_add; + Htree2 *htree_in_data; + Htree2 *htree_out_data; + Htree2 *htree_in_search; + Htree2 *htree_out_search; + + int num_addr_b_mat; + int num_mats_hor_dir; + int num_mats_ver_dir; + + int num_addr_b_row_dec; + int num_addr_b_routed_to_mat_for_act; + int num_addr_b_routed_to_mat_for_rd_or_wr; + + double array_leakage; + double wl_leakage; + double cl_leakage; +}; + + + +#endif diff --git a/Project_FARSI/cacti_for_FARSI/basic_circuit.cc b/Project_FARSI/cacti_for_FARSI/basic_circuit.cc new file mode 100644 index 00000000..696f45c4 --- /dev/null +++ b/Project_FARSI/cacti_for_FARSI/basic_circuit.cc @@ -0,0 +1,999 @@ +/***************************************************************************** + * CACTI 7.0 + * SOFTWARE LICENSE AGREEMENT + * Copyright 2015 Hewlett-Packard Development Company, L.P. + * All Rights Reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.” + * + ***************************************************************************/ + + + + +#include "basic_circuit.h" +#include "parameter.h" +#include +#include +#include + +uint32_t _log2(uint64_t num) +{ + uint32_t log2 = 0; + + if (num == 0) + { + std::cerr << "log0?" << std::endl; + exit(1); + } + + while (num > 1) + { + num = (num >> 1); + log2++; + } + + return log2; +} + + +bool is_pow2(int64_t val) +{ + if (val <= 0) + { + return false; + } + else if (val == 1) + { + return true; + } + else + { + return (_log2(val) != _log2(val-1)); + } +} + + +int powers (int base, int n) +{ + int i, p; + + p = 1; + for (i = 1; i <= n; ++i) + p *= base; + return p; +} + +/*----------------------------------------------------------------------*/ + +double logtwo (double x) +{ + assert(x > 0); + return ((double) (log (x) / log (2.0))); +} + +/*----------------------------------------------------------------------*/ + + +double gate_C( + double width, + double wirelength, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + const /*TechnologyParameter::*/DeviceType * dt; + + if (_is_dram && _is_cell) + { + dt = &g_tp.dram_acc; //DRAM cell access transistor + } + else if (_is_dram && _is_wl_tr) + { + dt = &g_tp.dram_wl; //DRAM wordline transistor + } + else if (!_is_dram && _is_cell) + { + dt = &g_tp.sram_cell; // SRAM cell access transistor + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { + dt = &g_tp.peri_global; + } + + return (dt->C_g_ideal + dt->C_overlap + 3*dt->C_fringe)*width + dt->l_phy*Cpolywire; +} + + +// returns gate capacitance in Farads +// actually this function is the same as gate_C() now +double gate_C_pass( + double width, // gate width in um (length is Lphy_periph_global) + double wirelength, // poly wire length going to gate in lambda + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + // v5.0 + const /*TechnologyParameter::*/DeviceType * dt; + + if ((_is_dram) && (_is_cell)) + { + dt = &g_tp.dram_acc; //DRAM cell access transistor + } + else if ((_is_dram) && (_is_wl_tr)) + { + dt = &g_tp.dram_wl; //DRAM wordline transistor + } + else if ((!_is_dram) && _is_cell) + { + dt = &g_tp.sram_cell; // SRAM cell access transistor + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { + dt = &g_tp.peri_global; + } + + return (dt->C_g_ideal + dt->C_overlap + 3*dt->C_fringe)*width + dt->l_phy*Cpolywire; +} + + + +double drain_C_( + double width, + int nchannel, + int stack, + int next_arg_thresh_folding_width_or_height_cell, + double fold_dimension, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + double w_folded_tr; + const /*TechnologyParameter::*/DeviceType * dt; + + if ((_is_dram) && (_is_cell)) + { + dt = &g_tp.dram_acc; // DRAM cell access transistor + } + else if ((_is_dram) && (_is_wl_tr)) + { + dt = &g_tp.dram_wl; // DRAM wordline transistor + } + else if ((!_is_dram) && _is_cell) + { + dt = &g_tp.sram_cell; // SRAM cell access transistor + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { + dt = &g_tp.peri_global; + } + + double c_junc_area = dt->C_junc; + double c_junc_sidewall = dt->C_junc_sidewall; + double c_fringe = 2*dt->C_fringe; + double c_overlap = 2*dt->C_overlap; + double drain_C_metal_connecting_folded_tr = 0; + + // determine the width of the transistor after folding (if it is getting folded) + if (next_arg_thresh_folding_width_or_height_cell == 0) + { // interpret fold_dimension as the the folding width threshold + // i.e. the value of transistor width above which the transistor gets folded + w_folded_tr = fold_dimension; + } + else + { // interpret fold_dimension as the height of the cell that this transistor is part of. + double h_tr_region = fold_dimension - 2 * g_tp.HPOWERRAIL; + // TODO : w_folded_tr must come from Component::compute_gate_area() + double ratio_p_to_n = 2.0 / (2.0 + 1.0); + if (nchannel) + { + w_folded_tr = (1 - ratio_p_to_n) * (h_tr_region - g_tp.MIN_GAP_BET_P_AND_N_DIFFS); + } + else + { + w_folded_tr = ratio_p_to_n * (h_tr_region - g_tp.MIN_GAP_BET_P_AND_N_DIFFS); + } + } + int num_folded_tr = (int) (ceil(width / w_folded_tr)); + + if (num_folded_tr < 2) + { + w_folded_tr = width; + } + + double total_drain_w = (g_tp.w_poly_contact + 2 * g_tp.spacing_poly_to_contact) + // only for drain + (stack - 1) * g_tp.spacing_poly_to_poly; + double drain_h_for_sidewall = w_folded_tr; + double total_drain_height_for_cap_wrt_gate = w_folded_tr + 2 * w_folded_tr * (stack - 1); + if (num_folded_tr > 1) + { + total_drain_w += (num_folded_tr - 2) * (g_tp.w_poly_contact + 2 * g_tp.spacing_poly_to_contact) + + (num_folded_tr - 1) * ((stack - 1) * g_tp.spacing_poly_to_poly); + + if (num_folded_tr%2 == 0) + { + drain_h_for_sidewall = 0; + } + total_drain_height_for_cap_wrt_gate *= num_folded_tr; + drain_C_metal_connecting_folded_tr = g_tp.wire_local.C_per_um * total_drain_w; + } + + double drain_C_area = c_junc_area * total_drain_w * w_folded_tr; + double drain_C_sidewall = c_junc_sidewall * (drain_h_for_sidewall + 2 * total_drain_w); + double drain_C_wrt_gate = (c_fringe + c_overlap) * total_drain_height_for_cap_wrt_gate; + + return (drain_C_area + drain_C_sidewall + drain_C_wrt_gate + drain_C_metal_connecting_folded_tr); +} + + +double tr_R_on( + double width, + int nchannel, + int stack, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + const /*TechnologyParameter::*/DeviceType * dt; + + if ((_is_dram) && (_is_cell)) + { + dt = &g_tp.dram_acc; //DRAM cell access transistor + } + else if ((_is_dram) && (_is_wl_tr)) + { + dt = &g_tp.dram_wl; //DRAM wordline transistor + } + else if ((!_is_dram) && _is_cell) + { + dt = &g_tp.sram_cell; // SRAM cell access transistor + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { + dt = &g_tp.peri_global; + } + + double restrans = (nchannel) ? dt->R_nch_on : dt->R_pch_on; + return (stack * restrans / width); +} + + +/* This routine operates in reverse: given a resistance, it finds + * the transistor width that would have this R. It is used in the + * data wordline to estimate the wordline driver size. */ + +// returns width in um +double R_to_w( + double res, + int nchannel, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + const /*TechnologyParameter::*/DeviceType * dt; + + if ((_is_dram) && (_is_cell)) + { + dt = &g_tp.dram_acc; //DRAM cell access transistor + } + else if ((_is_dram) && (_is_wl_tr)) + { + dt = &g_tp.dram_wl; //DRAM wordline transistor + } + else if ((!_is_dram) && (_is_cell)) + { + dt = &g_tp.sram_cell; // SRAM cell access transistor + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { + dt = &g_tp.peri_global; + } + + double restrans = (nchannel) ? dt->R_nch_on : dt->R_pch_on; + return (restrans / res); +} + + +double pmos_to_nmos_sz_ratio( + bool _is_dram, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + double p_to_n_sizing_ratio; + if ((_is_dram) && (_is_wl_tr)) + { //DRAM wordline transistor + p_to_n_sizing_ratio = g_tp.dram_wl.n_to_p_eff_curr_drv_ratio; + } + else if (_is_sleep_tx) + { + p_to_n_sizing_ratio = g_tp.sleep_tx.n_to_p_eff_curr_drv_ratio; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + p_to_n_sizing_ratio = g_tp.peri_global.n_to_p_eff_curr_drv_ratio; + } + return p_to_n_sizing_ratio; +} + + +// "Timing Models for MOS Circuits" by Mark Horowitz, 1984 +double horowitz( + double inputramptime, // input rise time + double tf, // time constant of gate + double vs1, // threshold voltage + double vs2, // threshold voltage + int rise) // whether input rises or fall +{ + if (inputramptime == 0 && vs1 == vs2) + { + return tf * (vs1 < 1 ? -log(vs1) : log(vs1)); + } + double a, b, td; + + a = inputramptime / tf; + if (rise == RISE) + { + b = 0.5; + td = tf * sqrt(log(vs1)*log(vs1) + 2*a*b*(1.0 - vs1)) + tf*(log(vs1) - log(vs2)); + } + else + { + b = 0.4; + td = tf * sqrt(log(1.0 - vs1)*log(1.0 - vs1) + 2*a*b*(vs1)) + tf*(log(1.0 - vs1) - log(1.0 - vs2)); + } + return (td); +} + +double cmos_Ileak( + double nWidth, + double pWidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return nWidth*dt->I_off_n + pWidth*dt->I_off_p; +} + +int factorial(int n, int m) +{ + int fa = m, i; + for (i=m+1; i<=n; i++) + fa *=i; + return fa; +} + +int combination(int n, int m) +{ + int ret; + ret = factorial(n, m+1) / factorial(n - m); + return ret; +} + +double simplified_nmos_Isat( + double nwidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return nwidth * dt->I_on_n; +} + +double simplified_pmos_Isat( + double pwidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return pwidth * dt->I_on_n/dt->n_to_p_eff_curr_drv_ratio; +} + + +double simplified_nmos_leakage( + double nwidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return nwidth * dt->I_off_n; +} + +double simplified_pmos_leakage( + double pwidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return pwidth * dt->I_off_p; +} + +double cmos_Ig_n( + double nWidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return nWidth*dt->I_g_on_n; +} + +double cmos_Ig_p( + double pWidth, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx) +{ + /*TechnologyParameter::*/DeviceType * dt; + + if ((!_is_dram)&&(_is_cell)) + { //SRAM cell access transistor + dt = &(g_tp.sram_cell); + } + else if ((_is_dram)&&(_is_wl_tr)) + { //DRAM wordline transistor + dt = &(g_tp.dram_wl); + } + else if (_is_sleep_tx) + { + dt = &g_tp.sleep_tx; // Sleep transistor + } + else + { //DRAM or SRAM all other transistors + dt = &(g_tp.peri_global); + } + return pWidth*dt->I_g_on_p; +} + +double cmos_Isub_leakage( + double nWidth, + double pWidth, + int fanin, + enum Gate_type g_type, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx, + enum Half_net_topology topo) +{ + assert (fanin>=1); + double nmos_leak = simplified_nmos_leakage(nWidth, _is_dram, _is_cell, _is_wl_tr, _is_sleep_tx); + double pmos_leak = simplified_pmos_leakage(pWidth, _is_dram, _is_cell, _is_wl_tr, _is_sleep_tx); + double Isub=0; + int num_states; + int num_off_tx; + + num_states = int(pow(2.0, fanin)); + + switch (g_type) + { + case nmos: + if (fanin==1) + { + Isub = nmos_leak/num_states; + } + else + { + if (topo==parallel) + { + Isub=nmos_leak*fanin/num_states; //only when all tx are off, leakage power is non-zero. The possibility of this state is 1/num_states + } + else + { + for (num_off_tx=1; num_off_tx<=fanin; num_off_tx++) //when num_off_tx ==0 there is no leakage power + { + //Isub += nmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*(factorial(fanin)/(factorial(fanin, num_off_tx)*factorial(num_off_tx))); + Isub += nmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*combination(fanin, num_off_tx); + } + Isub /=num_states; + } + + } + break; + case pmos: + if (fanin==1) + { + Isub = pmos_leak/num_states; + } + else + { + if (topo==parallel) + { + Isub=pmos_leak*fanin/num_states; //only when all tx are off, leakage power is non-zero. The possibility of this state is 1/num_states + } + else + { + for (num_off_tx=1; num_off_tx<=fanin; num_off_tx++) //when num_off_tx ==0 there is no leakage power + { + //Isub += pmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*(factorial(fanin)/(factorial(fanin, num_off_tx)*factorial(num_off_tx))); + Isub += pmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*combination(fanin, num_off_tx); + } + Isub /=num_states; + } + + } + break; + case inv: + Isub = (nmos_leak + pmos_leak)/2; + break; + case nand: + Isub += fanin*pmos_leak;//the pullup network + for (num_off_tx=1; num_off_tx<=fanin; num_off_tx++) // the pulldown network + { + //Isub += nmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*(factorial(fanin)/(factorial(fanin, num_off_tx)*factorial(num_off_tx))); + Isub += nmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*combination(fanin, num_off_tx); + } + Isub /=num_states; + break; + case nor: + for (num_off_tx=1; num_off_tx<=fanin; num_off_tx++) // the pullup network + { + //Isub += pmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*(factorial(fanin)/(factorial(fanin, num_off_tx)*factorial(num_off_tx))); + Isub += pmos_leak*pow(UNI_LEAK_STACK_FACTOR,(num_off_tx-1))*combination(fanin, num_off_tx); + } + Isub += fanin*nmos_leak;//the pulldown network + Isub /=num_states; + break; + case tri: + Isub += (nmos_leak + pmos_leak)/2;//enabled + Isub += nmos_leak*UNI_LEAK_STACK_FACTOR; //disabled upper bound of leakage power + Isub /=2; + break; + case tg: + Isub = (nmos_leak + pmos_leak)/2; + break; + default: + assert(0); + break; + } + + return Isub; +} + + +double cmos_Ig_leakage( + double nWidth, + double pWidth, + int fanin, + enum Gate_type g_type, + bool _is_dram, + bool _is_cell, + bool _is_wl_tr, + bool _is_sleep_tx, + enum Half_net_topology topo) +{ + assert (fanin>=1); + double nmos_leak = cmos_Ig_n(nWidth, _is_dram, _is_cell, _is_wl_tr, _is_sleep_tx); + double pmos_leak = cmos_Ig_p(pWidth, _is_dram, _is_cell, _is_wl_tr, _is_sleep_tx); + double Ig_on=0; + int num_states; + int num_on_tx; + + num_states = int(pow(2.0, fanin)); + + switch (g_type) + { + case nmos: + if (fanin==1) + { + Ig_on = nmos_leak/num_states; + } + else + { + if (topo==parallel) + { + for (num_on_tx=1; num_on_tx<=fanin; num_on_tx++) + { + Ig_on += nmos_leak*combination(fanin, num_on_tx)*num_on_tx; + } + } + else + { + Ig_on += nmos_leak * fanin;//pull down network when all TXs are on. + //num_on_tx is the number of on tx + for (num_on_tx=1; num_on_txprint_detail_debug) + { + cout<<"TSV ox cap: "<1 transitions) per cycle (for DDR, need to account for the higher activity in this parameter. E.g. max. activity factor for DDR is 1.0, for SDR is 0.5) + +-activity_dq 1.0 //Valid range 0 to 1.0 for DDR, 0 to 0.5 for SDR + +# Activity factor for Control/Address (0->1 transitions) per cycle (for DDR, need to account for the higher activity in this parameter. E.g. max. activity factor for DDR is 1.0, for SDR is 0.5) + +-activity_ca 0.5 //Valid range 0 to 1.0 for DDR, 0 to 0.5 for SDR, 0 to 0.25 for 2T, and 0 to 0.17 for 3T + +# Number of DQ pins + +-num_dq 72 //Number of DQ pins. Includes ECC pins. + +# Number of DQS pins. DQS is a data strobe that is sent along with a small number of data-lanes so the source synchronous timing is local to these DQ bits. Typically, 1 DQS per byte (8 DQ bits) is used. The DQS is also typucally differential, just like the CLK pin. + +-num_dqs 18 //2 x differential pairs. Include ECC pins as well. Valid range 0 to 18. For x4 memories, could have 36 DQS pins. + +# Number of CA pins + +-num_ca 25 //Valid range 0 to 35 pins. + +# Number of CLK pins. CLK is typically a differential pair. In some cases additional CLK pairs may be used to limit the loading on the CLK pin. + +-num_clk 2 //2 x differential pair. Valid values: 0/2/4. + +# Number of Physical Ranks + +-num_mem_dq 2 //Number of ranks (loads on DQ and DQS) per buffer/register. If multiple LRDIMMs or buffer chips exist, the analysis for capacity and power is reported per buffer/register. + +# Width of the Memory Data Bus + +-mem_data_width 8 //x4 or x8 or x16 or x32 memories. For WideIO upto x128. + +# RTT Termination Resistance + +-rtt_value 10000 + +# RON Termination Resistance + +-ron_value 34 + +# Time of flight for DQ + +-tflight_value + +# Parameter related to MemCAD + +# Number of BoBs: 1,2,3,4,5,6, +-num_bobs 1 + +# Memory System Capacity in GB +-capacity 80 + +# Number of Channel per BoB: 1,2. +-num_channels_per_bob 1 + +# First Metric for ordering different design points +-first metric "Cost" +#-first metric "Bandwidth" +#-first metric "Energy" + +# Second Metric for ordering different design points +#-second metric "Cost" +-second metric "Bandwidth" +#-second metric "Energy" + +# Third Metric for ordering different design points +#-third metric "Cost" +#-third metric "Bandwidth" +-third metric "Energy" + + +# Possible DIMM option to consider +#-DIMM model "JUST_UDIMM" +#-DIMM model "JUST_RDIMM" +#-DIMM model "JUST_LRDIMM" +-DIMM model "ALL" + +#if channels of each bob have the same configurations +#-mirror_in_bob "T" +-mirror_in_bob "F" + +#if we want to see all channels/bobs/memory configurations explored +#-verbose "T" +#-verbose "F" + diff --git a/Project_FARSI/cacti_for_FARSI/cacti b/Project_FARSI/cacti_for_FARSI/cacti new file mode 100755 index 0000000000000000000000000000000000000000..334437c32dec159f0c55ff5be9b529ae49241e62 GIT binary patch literal 2605680 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%0A2SBJ2p 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%0AZ5-f~B~vJc43uJTo! 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 z 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 z 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
u(cLA&W8SIRx`(B0K_;Eg1&yb{ z`h+we>eQs(J%=- z4v^VA$WlUW0Gt+eTfU-!*#PCofh@%!YCfj3uZ_CBFW12C01b)*D@859hokO;=yVwM z#Q_oc ziPn!P?HQiC`gRShT7dZ<{yP7a63WMP_6?r^FYqhf&i#(|X*D!|pAJLy6VoCMJ9 zIPlDb3i?IQ-EzK0F9veeKhgRzrTx0+R_xcntpL3h2UbcbAJf?%dG3s$Tlf~BlW}0B zgbMI)&wb$?tuYO=$wv79d(jIz*>`_hrP0Me_WdVXKc=*Ee0L+dJsN5fKv%|rl@iKL z6?QA%UDgguKw0P&0eUD7q-fAQxRt@~;k!M~MURNaxCx-0aUgSK$W{jXG2b19;vwc! z@PCeD;>v;%RW!chyWP+q5$$8*o{zuI`srv%MCFwH$9G>^qvg6orf*!1WgDdA2j4Aw zRbwWBKRb>|=&pp0Cb|A)8oe0EHU9@%CnELPpJ9#))18G!DLVoEi17H`r_B8H%GW<1 zr=IPjxyjR}WLn&4KLQy^t=+==FUmp|b8ENLJO83XFk!CUF+Bo?ex6giZI!3lS`Z6Am(X;1aRf*=CM;BPfRi75IMF?!v%cA?f=Qv|xEU5+xn9^Cmc0i!CH zN^19X&z+8)I0#*V|0;5uuz+RzSnb~KT7*#b%lPkzb7lKj?Y^1&HS=RIK94h-k2)*Z z{>K5l%-W30*te1R8u=LJ&dH%Al79yrXABK)W%THmIqt@ElmhO8=Bt4rWTWmirl+0& zJ)Yzob6N!LALLp1TdkSA@dLn^Yx3))

-XW9TKuTSQIW8%(zsDw|T*fXywOBCCq1 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

QD*7gIcZviD)YDe2()uJ?Su#*k7{ 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