From b7891404d396f840ecd1e33f572a68f6f6562b14 Mon Sep 17 00:00:00 2001 From: Ayaka Yorihiro <36107281+ayakayorihiro@users.noreply.github.com> Date: Mon, 27 May 2024 11:48:09 -0400 Subject: [PATCH] [Profiling] Modifications to TDCC and scripts for first pass profiling (#2040) * add new option * Barebones hack version created... Now I want to write to a file * Fixing clippy errors * Small scripts for first pass VCD parsing * very hacky way to only get group names * Bare bones python script to output group to number of cycles * Fix clippy errors * Output JSON to file * remove unused import * Remove hack for obtaining group to states * Small wrapper script to run TDCC, simulation, and output cycle counts * Fix clippy errors * Small changes to scripts * Get Enables to directly produce FSMInfo * First pass code for collecting FSMInfos across multiple TDCC groups * Fix clippy errors * remove debugging prints * Clean things up before making PR * Clean up more comments * Allow passing Calyx arguments from command line * Update fud2 tests to have empty args variable * Replace script to get no-opt vcd with fud2 command * README for scripts in tools/vcd-parsing * Documentation comments for new structs * Use Id instead of String for JSON structs --- .gitignore | 6 +- .../src/passes/top_down_compile_control.rs | 85 ++++++++++++++++--- calyx-utils/src/id.rs | 2 + fud2/src/lib.rs | 1 + .../snapshots/tests__emit@calyx_debug.snap | 1 + .../tests__emit@calyx_firrtl_verilog.snap | 1 + .../tests__emit@calyx_icarus_dat.snap | 1 + .../tests__emit@calyx_icarus_vcd.snap | 1 + .../tests__emit@calyx_interp_dat.snap | 1 + .../tests__emit@calyx_verilator_dat.snap | 1 + .../tests__emit@calyx_verilator_vcd.snap | 1 + .../snapshots/tests__emit@calyx_verilog.snap | 1 + .../tests__emit@calyx_xrt-trace_vcd.snap | 1 + .../snapshots/tests__emit@calyx_xrt_dat.snap | 1 + tools/vcd-parsing/README.md | 13 +++ tools/vcd-parsing/get-profile-counts-info.sh | 44 ++++++++++ tools/vcd-parsing/parse-vcd.py | 47 ++++++++++ 17 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 tools/vcd-parsing/README.md create mode 100644 tools/vcd-parsing/get-profile-counts-info.sh create mode 100644 tools/vcd-parsing/parse-vcd.py diff --git a/.gitignore b/.gitignore index 332526c53f..00ae663a94 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,8 @@ results.xml !cider-dap/calyxDebug/tsconfig.json # btor2i ignore -tools/btor2/btor2i/build/ \ No newline at end of file +tools/btor2/btor2i/build/ + +# vcd-parsing tmp files ignore +tools/vcd-parsing/tmp/ +tools/vcd-parsing/logs diff --git a/calyx-opt/src/passes/top_down_compile_control.rs b/calyx-opt/src/passes/top_down_compile_control.rs index 8a95ddd983..836eaf9a7c 100644 --- a/calyx-opt/src/passes/top_down_compile_control.rs +++ b/calyx-opt/src/passes/top_down_compile_control.rs @@ -4,13 +4,14 @@ use crate::traversal::{ Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor, }; use calyx_ir::{self as ir, GetAttributes, LibrarySignatures, Printer, RRC}; -use calyx_ir::{build_assignments, guard, structure}; -use calyx_utils::CalyxResult; +use calyx_ir::{build_assignments, guard, structure, Id}; use calyx_utils::Error; +use calyx_utils::{CalyxResult, OutputFile}; use ir::Nothing; use itertools::Itertools; use petgraph::graph::DiGraph; -use std::collections::HashMap; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; use std::io::Write; use std::rc::Rc; @@ -210,6 +211,8 @@ fn compute_unique_ids(con: &mut ir::Control, cur_state: u64) -> u64 { /// Represents the dyanmic execution schedule of a control program. struct Schedule<'b, 'a: 'b> { + /// A mapping from groups to corresponding FSM state ids + pub groups_to_states: HashSet, /// Assigments that should be enabled in a given state. pub enables: HashMap>>, /// Transition from one state to another when the guard is true. @@ -219,9 +222,35 @@ struct Schedule<'b, 'a: 'b> { pub builder: &'b mut ir::Builder<'a>, } +/// Information to be serialized for a single FSM +#[derive(PartialEq, Eq, Hash, Clone, Serialize)] +struct FSMInfo { + #[serde(serialize_with = "id_serialize_passthrough")] + pub component: Id, + #[serde(serialize_with = "id_serialize_passthrough")] + pub group: Id, + pub states: Vec, +} + +/// Mapping of FSM state ids to corresponding group names +#[derive(PartialEq, Eq, Hash, Clone, Serialize)] +struct FSMStateInfo { + id: u64, + #[serde(serialize_with = "id_serialize_passthrough")] + group: Id, +} + +fn id_serialize_passthrough(id: &Id, ser: S) -> Result +where + S: serde::Serializer, +{ + id.to_string().serialize(ser) +} + impl<'b, 'a> From<&'b mut ir::Builder<'a>> for Schedule<'b, 'a> { fn from(builder: &'b mut ir::Builder<'a>) -> Self { Schedule { + groups_to_states: HashSet::new(), enables: HashMap::new(), transitions: Vec::new(), builder, @@ -279,7 +308,11 @@ impl<'b, 'a> Schedule<'b, 'a> { /// Implement a given [Schedule] and return the name of the [ir::Group] that /// implements it. - fn realize_schedule(self, dump_fsm: bool) -> RRC { + fn realize_schedule( + self, + dump_fsm: bool, + fsm_groups: &mut HashSet, + ) -> RRC { self.validate(); let group = self.builder.add_group("tdcc"); @@ -291,6 +324,13 @@ impl<'b, 'a> Schedule<'b, 'a> { )); } + // Keep track of groups to FSM state id information for dumping to json + fsm_groups.insert(FSMInfo { + component: self.builder.component.name, + group: group.borrow().name(), + states: self.groups_to_states.iter().cloned().collect_vec(), + }); + let final_state = self.last_state(); let fsm_size = get_bit_width_from( final_state + 1, /* represent 0..final_state */ @@ -397,6 +437,7 @@ impl Schedule<'_, '_> { match con { // See explanation of FSM states generated in [ir::TopDownCompileControl]. ir::Control::Enable(ir::Enable { group, attributes }) => { + let cur_state = attributes.get(NODE_ID).unwrap_or_else(|| panic!("Group `{}` does not have node_id information", group.borrow().name())); // If there is exactly one previous transition state with a `true` // guard, then merge this state into previous state. @@ -408,6 +449,9 @@ impl Schedule<'_, '_> { (cur_state, preds) }; + // Add group to mapping for emitting group JSON info + self.groups_to_states.insert(FSMStateInfo { id: cur_state, group: group.borrow().name() }); + let not_done = !guard!(group["done"]); let signal_on = self.builder.add_constant(1, 1); @@ -770,8 +814,12 @@ impl Schedule<'_, '_> { pub struct TopDownCompileControl { /// Print out the FSM representation to STDOUT dump_fsm: bool, + /// Output a JSON FSM representation to file if specified + dump_fsm_json: Option, /// Enable early transitions early_transitions: bool, + /// Bookkeeping for FSM ids for groups across all FSMs in the program + fsm_groups: HashSet, } impl ConstructVisitor for TopDownCompileControl { @@ -783,7 +831,9 @@ impl ConstructVisitor for TopDownCompileControl { Ok(TopDownCompileControl { dump_fsm: opts[&"dump-fsm"].bool(), + dump_fsm_json: opts[&"dump-fsm-json"].not_null_outstream(), early_transitions: opts[&"early-transitions"].bool(), + fsm_groups: HashSet::new(), }) } @@ -809,6 +859,12 @@ impl Named for TopDownCompileControl { ParseVal::Bool(false), PassOpt::parse_bool, ), + PassOpt::new( + "dump-fsm-json", + "Write the state machine implementing the schedule to a JSON file", + ParseVal::OutStream(OutputFile::Null), + PassOpt::parse_outstream, + ), PassOpt::new( "early-transitions", "Experimental: Enable early transitions for group enables", @@ -855,7 +911,8 @@ impl Visitor for TopDownCompileControl { let mut sch = Schedule::from(&mut builder); sch.calculate_states_seq(s, self.early_transitions)?; // Compile schedule and return the group. - let seq_group = sch.realize_schedule(self.dump_fsm); + let seq_group = + sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups); // Add NODE_ID to compiled group. let mut en = ir::Control::enable(seq_group); @@ -881,7 +938,8 @@ impl Visitor for TopDownCompileControl { // Compile schedule and return the group. sch.calculate_states_if(i, self.early_transitions)?; - let if_group = sch.realize_schedule(self.dump_fsm); + let if_group = + sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups); // Add NODE_ID to compiled group. let mut en = ir::Control::enable(if_group); @@ -907,7 +965,8 @@ impl Visitor for TopDownCompileControl { sch.calculate_states_while(w, self.early_transitions)?; // Compile schedule and return the group. - let if_group = sch.realize_schedule(self.dump_fsm); + let if_group = + sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups); // Add NODE_ID to compiled group. let mut en = ir::Control::enable(if_group); @@ -949,7 +1008,7 @@ impl Visitor for TopDownCompileControl { _ => { let mut sch = Schedule::from(&mut builder); sch.calculate_states(con, self.early_transitions)?; - sch.realize_schedule(self.dump_fsm) + sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups) } }; @@ -1020,8 +1079,14 @@ impl Visitor for TopDownCompileControl { let mut sch = Schedule::from(&mut builder); // Add assignments for the final states sch.calculate_states(&control.borrow(), self.early_transitions)?; - let comp_group = sch.realize_schedule(self.dump_fsm); - + let comp_group = + sch.realize_schedule(self.dump_fsm, &mut self.fsm_groups); + if let Some(json_out_file) = &self.dump_fsm_json { + let _ = serde_json::to_writer_pretty( + json_out_file.get_write(), + &self.fsm_groups, + ); + } Ok(Action::change(ir::Control::enable(comp_group))) } } diff --git a/calyx-utils/src/id.rs b/calyx-utils/src/id.rs index c88cdafa30..7dfc37ea0f 100644 --- a/calyx-utils/src/id.rs +++ b/calyx-utils/src/id.rs @@ -1,3 +1,5 @@ +// use serde::{Serialize, Serializer}; + pub type GSym = symbol_table::GlobalSymbol; /// Represents an identifier in a Calyx program diff --git a/fud2/src/lib.rs b/fud2/src/lib.rs index 4595daffe9..15e673bb0a 100644 --- a/fud2/src/lib.rs +++ b/fud2/src/lib.rs @@ -16,6 +16,7 @@ fn setup_calyx( "calyx.exe", "$calyx-base/target/debug/calyx", )?; + e.config_var_or("args", "calyx.args", "")?; e.rule( "calyx", "$calyx-exe -l $calyx-base -b $backend $args $in > $out", diff --git a/fud2/tests/snapshots/tests__emit@calyx_debug.snap b/fud2/tests/snapshots/tests__emit@calyx_debug.snap index ae6294e1d9..2301d912dc 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_debug.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_debug.snap @@ -24,6 +24,7 @@ cycle-limit = 500000000 # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap index c7f16b193c..6feb6af95d 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap index b6818c34e0..9ecd9b6e2d 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap index 42f65bcff8..9b4db5ebff 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap index 4f52d3e7bf..0ef04007f1 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap @@ -24,6 +24,7 @@ cycle-limit = 500000000 # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap index 81f08e088b..e7a2aaeafc 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap index f422e74d56..8d4cbc0311 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilog.snap b/fud2/tests/snapshots/tests__emit@calyx_verilog.snap index ea7e31437c..de7b7f66f2 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilog.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilog.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap index b574858901..f38eeb68fb 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap index 21d5ca088b..a98882ad27 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap @@ -9,6 +9,7 @@ rule get-rsrc # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx +args = rule calyx command = $calyx-exe -l $calyx-base -b $backend $args $in > $out rule calyx-pass diff --git a/tools/vcd-parsing/README.md b/tools/vcd-parsing/README.md new file mode 100644 index 0000000000..be7bb3c0b6 --- /dev/null +++ b/tools/vcd-parsing/README.md @@ -0,0 +1,13 @@ +# Profiling Scripts + +This directory contains scripts for a first pass at profiling cycle counts in Calyx programs. It contains: + +- `get-profile-counts-info.sh`: A wrapper script that produces a cycle counts estimate given a Calyx program +- `parse-vcd.py`: A helper script that reads in a VCD file and a JSON FSM file to generate cycle count estimates + +### Usage + +- To run the profiling pipeline, you can run `get-profile-counts-info.sh` providing the Calyx file and the Memory data. ex) From the Calyx root directory +``` +bash tools/vcd-parsing/get-profile-counts-info.sh examples/tutorial/language-tutorial-compute.futil examples/tutorial/data.json +``` diff --git a/tools/vcd-parsing/get-profile-counts-info.sh b/tools/vcd-parsing/get-profile-counts-info.sh new file mode 100644 index 0000000000..5ab197207c --- /dev/null +++ b/tools/vcd-parsing/get-profile-counts-info.sh @@ -0,0 +1,44 @@ +# Wrapper script for running TDCC, running simulation, and obtaining cycle counts information + +if [ $# -ne 2 ]; then + echo "USAGE: bash $0 INPUT_FILE SIM_DATA_JSON" + exit +fi + +SCRIPT_DIR=$( cd $( dirname $0 ) && pwd ) +SCRIPT_NAME=$( echo "$0" | rev | cut -d/ -f1 | rev ) +CALYX_DIR=$( dirname $( dirname ${SCRIPT_DIR} ) ) +TMP_DIR=${SCRIPT_DIR}/tmp +TMP_VERILOG=${TMP_DIR}/no-opt-verilog.sv +FSM_JSON=${TMP_DIR}/fsm.json +VCD_FILE=${TMP_DIR}/trace-info.vcd +LOGS_DIR=${SCRIPT_DIR}/logs +mkdir -p ${TMP_DIR} ${LOGS_DIR} + +rm -f ${TMP_VERILOG} ${FSM_JSON} + +INPUT_FILE=$1 +SIM_DATA_JSON=$2 + +# Run TDCC to get the FSM info +echo "[${SCRIPT_NAME}] Obtaining FSM info from TDCC" +( + cd ${CALYX_DIR} + set -o xtrace + cargo run -- ${INPUT_FILE} -p no-opt -x tdcc:dump-fsm-json="${FSM_JSON}" + set +o xtrace +) &> ${LOGS_DIR}/gol-tdcc + +# Run simuation to get VCD +echo "[${SCRIPT_NAME}] Obtaining VCD file via simulation" +( + set -o xtrace + fud2 ${INPUT_FILE} -o ${VCD_FILE} -s calyx.args='-p no-opt' -s sim.data=${SIM_DATA_JSON} + set +o xtrace +) &> ${LOGS_DIR}/gol-vcd + +# Run script to get cycle level counts +echo "[${SCRIPT_NAME}] Using FSM info and VCD file to obtain cycle level counts" +( + python3 ${SCRIPT_DIR}/parse-vcd.py ${VCD_FILE} ${FSM_JSON} +) # &> ${LOGS_DIR}/gol-process diff --git a/tools/vcd-parsing/parse-vcd.py b/tools/vcd-parsing/parse-vcd.py new file mode 100644 index 0000000000..bb317e0835 --- /dev/null +++ b/tools/vcd-parsing/parse-vcd.py @@ -0,0 +1,47 @@ +import sys +import json +from vcdvcd import VCDVCD + +def remap_tdcc_json(json_file): + tdcc_json = json.load(open(json_file)) + tdcc_json = tdcc_json[0] # FIXME: we assume that the program yields only one FSM. + component_name = tdcc_json["component"] + tdcc_remap = {} + for state in tdcc_json["states"]: + tdcc_remap[state["id"]] = state["group"] + return component_name, tdcc_remap + +def main(vcd_filename, json_file): + component_name, fsm_values = remap_tdcc_json(json_file) + vcd = VCDVCD(vcd_filename) + fsm_val_to_num_cycles = {} + for key in vcd.references_to_ids.keys(): + if f"{component_name}.fsm_out" in key: + signal = vcd[key] + fsm_value = -1 + fsm_time_start = -1 + for signal_value_tuple in signal.tv: + curr_time, binary_fsm_val = signal_value_tuple + if fsm_value >= 0: + # FIXME: For now, will assume that each clock cycle takes 10 ms + fsm_val_to_num_cycles[fsm_values[fsm_value]] = int((curr_time - fsm_time_start) / 10) + fsm_value = int(binary_fsm_val, 2) + fsm_time_start = curr_time + + print(fsm_val_to_num_cycles) + + +if __name__ == "__main__": + + if len(sys.argv) > 2: + vcd_filename = sys.argv[1] + fsm_json = sys.argv[2] + main(vcd_filename, fsm_json) + else: + args_desc = [ + "VCD_FILE", + "TDCC_JSON" + ] + print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}") + sys.exit(-1) +