diff --git a/Cargo.lock b/Cargo.lock index 6f58dd2a5..cdffa8954 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -729,19 +729,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "component_groups" -version = "0.7.1" -dependencies = [ - "argh", - "calyx-frontend", - "calyx-ir", - "calyx-opt", - "calyx-utils", - "serde", - "serde_json", -] - [[package]] name = "console" version = "0.15.8" diff --git a/Cargo.toml b/Cargo.toml index aa8b637a4..d509d9a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,6 @@ members = [ "tools/calyx-pass-explorer", "tools/cider-data-converter", "tools/component_cells", - "tools/component_groups", "tools/yxi", "tools/calyx-writer", ] diff --git a/runt.toml b/runt.toml index 08b46395b..b4a925053 100644 --- a/runt.toml +++ b/runt.toml @@ -693,4 +693,4 @@ name = "profiler" paths = ["tests/profiler/*.futil"] cmd = """ bash tools/profiler/get-profile-counts-info.sh {} {}.data STDOUT -d -""" +""" \ No newline at end of file diff --git a/tests/profiler/par.expect b/tests/profiler/par.expect index ee7aa537c..bf4f60307 100644 --- a/tests/profiler/par.expect +++ b/tests/profiler/par.expect @@ -5,62 +5,65 @@ Total clock cycles: 3 =====SUMMARY===== -Group TOP.toplevel.main.par0 Summary: +[FSM] Group TOP.toplevel.main.par0 Summary: Total cycles: 3 # of times active: 1 Avg runtime: 3.0 -Group TOP.toplevel.main.wr_a Summary: +[GT] Group TOP.toplevel.main.par0 Summary: + Total cycles: 2 + # of times active: 1 + Avg runtime: 2.0 + +[GT] Group TOP.toplevel.main.wr_a Summary: Total cycles: 1 # of times active: 1 Avg runtime: 1.0 -Group TOP.toplevel.main.wr_b Summary: +[GT] Group TOP.toplevel.main.wr_b Summary: Total cycles: 1 # of times active: 1 Avg runtime: 1.0 -Group TOP.toplevel.main.wr_c Summary: +[GT] Group TOP.toplevel.main.wr_c Summary: Total cycles: 1 # of times active: 1 Avg runtime: 1.0 =====DUMP===== -Group TOP.toplevel.main.par0: +[FSM] Group TOP.toplevel.main.par0: FSM name: TOP.toplevel.main.fsm FSM state ids: [0] Total cycles: 3 # of times active: 1 Segments: [0, 3) -Group TOP.toplevel.main.wr_a: - FSM name: None - FSM state ids: None +[GT] Group TOP.toplevel.main.par0: + Total cycles: 2 + # of times active: 1 + Segments: [0, 2) + +[GT] Group TOP.toplevel.main.wr_a: Total cycles: 1 # of times active: 1 Segments: [0, 1) -Group TOP.toplevel.main.wr_b: - FSM name: None - FSM state ids: None +[GT] Group TOP.toplevel.main.wr_b: Total cycles: 1 # of times active: 1 Segments: [0, 1) -Group TOP.toplevel.main.wr_c: - FSM name: None - FSM state ids: None +[GT] Group TOP.toplevel.main.wr_c: Total cycles: 1 # of times active: 1 Segments: [0, 1) -Writing dump JSON to /home/ayaka/projects/calyx/tools/profiler/data//tmp/dump.json -Writing summary to STDOUT name,total-cycles,times-active,avg -TOP.toplevel.main.par0,3,1,3.0 +TOP.toplevel.main.par0[FSM],3,1,3.0 +TOP.toplevel.main.par0,2,1,2.0 TOP.toplevel.main.wr_a,1,1,1.0 TOP.toplevel.main.wr_b,1,1,1.0 TOP.toplevel.main.wr_c,1,1,1.0 TOTAL,3,-,- -[get-profile-counts-info.sh] Writing visualization to /home/ayaka/projects/calyx/tools/profiler/data//tmp/visual.json +[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/simple-par.expect b/tests/profiler/simple-par.expect index b2fccedf3..213fd3002 100644 --- a/tests/profiler/simple-par.expect +++ b/tests/profiler/simple-par.expect @@ -5,36 +5,43 @@ Total clock cycles: 3 =====SUMMARY===== -Group TOP.toplevel.main.par0 Summary: +[FSM] Group TOP.toplevel.main.par0 Summary: Total cycles: 3 # of times active: 1 Avg runtime: 3.0 -Group TOP.toplevel.main.write Summary: +[GT] Group TOP.toplevel.main.par0 Summary: + Total cycles: 2 + # of times active: 1 + Avg runtime: 2.0 + +[GT] Group TOP.toplevel.main.write Summary: Total cycles: 1 # of times active: 1 Avg runtime: 1.0 =====DUMP===== -Group TOP.toplevel.main.par0: +[FSM] Group TOP.toplevel.main.par0: FSM name: TOP.toplevel.main.fsm FSM state ids: [0] Total cycles: 3 # of times active: 1 Segments: [0, 3) -Group TOP.toplevel.main.write: - FSM name: None - FSM state ids: None +[GT] Group TOP.toplevel.main.par0: + Total cycles: 2 + # of times active: 1 + Segments: [0, 2) + +[GT] Group TOP.toplevel.main.write: Total cycles: 1 # of times active: 1 Segments: [0, 1) -Writing dump JSON to /home/ayaka/projects/calyx/tools/profiler/data//tmp/dump.json -Writing summary to STDOUT name,total-cycles,times-active,avg -TOP.toplevel.main.par0,3,1,3.0 +TOP.toplevel.main.par0[FSM],3,1,3.0 +TOP.toplevel.main.par0,2,1,2.0 TOP.toplevel.main.write,1,1,1.0 TOTAL,3,-,- -[get-profile-counts-info.sh] Writing visualization to /home/ayaka/projects/calyx/tools/profiler/data//tmp/visual.json +[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/simple-seq.expect b/tests/profiler/simple-seq.expect index cd145ed7b..35bff072b 100644 --- a/tests/profiler/simple-seq.expect +++ b/tests/profiler/simple-seq.expect @@ -5,23 +5,32 @@ Total clock cycles: 2 =====SUMMARY===== -Group TOP.toplevel.main.write Summary: +[FSM] Group TOP.toplevel.main.write Summary: Total cycles: 2 # of times active: 1 Avg runtime: 2.0 +[GT] Group TOP.toplevel.main.write Summary: + Total cycles: 1 + # of times active: 1 + Avg runtime: 1.0 + =====DUMP===== -Group TOP.toplevel.main.write: +[FSM] Group TOP.toplevel.main.write: FSM name: TOP.toplevel.main.fsm FSM state ids: [0] Total cycles: 2 # of times active: 1 Segments: [0, 2) -Writing dump JSON to /home/ayaka/projects/calyx/tools/profiler/data//tmp/dump.json -Writing summary to STDOUT +[GT] Group TOP.toplevel.main.write: + Total cycles: 1 + # of times active: 1 + Segments: [0, 1) + name,total-cycles,times-active,avg -TOP.toplevel.main.write,2,1,2.0 +TOP.toplevel.main.write[FSM],2,1,2.0 +TOP.toplevel.main.write,1,1,1.0 TOTAL,2,-,- -[get-profile-counts-info.sh] Writing visualization to /home/ayaka/projects/calyx/tools/profiler/data//tmp/visual.json +[get-profile-counts-info.sh] Writing visualization diff --git a/tests/profiler/while-never-true.expect b/tests/profiler/while-never-true.expect new file mode 100644 index 000000000..04bf0a1c9 --- /dev/null +++ b/tests/profiler/while-never-true.expect @@ -0,0 +1,60 @@ +[get-profile-counts-info.sh] Obtaining FSM info from TDCC +[get-profile-counts-info.sh] Obtaining cell information from component-cells backend +[get-profile-counts-info.sh] Obtaining VCD file via simulation +[get-profile-counts-info.sh] Using FSM info and VCD file to obtain cycle level counts +Total clock cycles: 2 +=====SUMMARY===== + +[FSM] Group TOP.toplevel.main.cond Summary: + Total cycles: 2 + # of times active: 1 + Avg runtime: 2.0 + +[GT] Group TOP.toplevel.main.cond Summary: + Total cycles: 1 + # of times active: 1 + Avg runtime: 1.0 + +[FSM] Group TOP.toplevel.main.incr Summary: + Total cycles: 0 + # of times active: 0 + Avg runtime: 0 + +[GT] Group TOP.toplevel.main.incr Summary: + Total cycles: 0 + # of times active: 0 + Avg runtime: 0 + +=====DUMP===== + +[FSM] Group TOP.toplevel.main.cond: + FSM name: TOP.toplevel.main.fsm + FSM state ids: [0, 3] + Total cycles: 2 + # of times active: 1 + Segments: [0, 2) + +[GT] Group TOP.toplevel.main.cond: + Total cycles: 1 + # of times active: 1 + Segments: [0, 1) + +[FSM] Group TOP.toplevel.main.incr: + FSM name: TOP.toplevel.main.fsm + FSM state ids: [2, 1] + Total cycles: 0 + # of times active: 0 + Segments: + +[GT] Group TOP.toplevel.main.incr: + Total cycles: 0 + # of times active: 0 + Segments: + +name,total-cycles,times-active,avg +TOP.toplevel.main.cond[FSM],2,1,2.0 +TOP.toplevel.main.cond,1,1,1.0 +TOP.toplevel.main.incr[FSM],0,0,0 +TOP.toplevel.main.incr,0,0,0 +TOTAL,2,-,- +[get-profile-counts-info.sh] Writing visualization diff --git a/tools/component_groups/Cargo.toml b/tools/component_groups/Cargo.toml deleted file mode 100644 index cbc2c2abb..000000000 --- a/tools/component_groups/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "component_groups" -authors.workspace = true -license-file.workspace = true -keywords.workspace = true -repository.workspace = true -readme.workspace = true -description.workspace = true -categories.workspace = true -homepage.workspace = true -edition.workspace = true -version.workspace = true -rust-version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde.workspace = true -argh.workspace = true -serde_json = "1.0.79" - -calyx-utils = { path = "../../calyx-utils" } -calyx-frontend = { path = "../../calyx-frontend" } -calyx-opt = { path = "../../calyx-opt" } - -[dependencies.calyx-ir] -path = "../../calyx-ir" -features = ["serialize"] diff --git a/tools/component_groups/src/main.rs b/tools/component_groups/src/main.rs deleted file mode 100644 index 79f9c3310..000000000 --- a/tools/component_groups/src/main.rs +++ /dev/null @@ -1,100 +0,0 @@ -use argh::FromArgs; -use calyx_frontend as frontend; -use calyx_ir::{self as ir, Id}; -use calyx_utils::{CalyxResult, OutputFile}; -use serde::Serialize; -use std::path::{Path, PathBuf}; -use std::{collections::HashSet, io}; - -#[derive(FromArgs)] -/// Path for library and path for file to read from -struct Args { - /// file path to read data from - #[argh(positional, from_str_fn(read_path))] - file_path: Option, - - /// library path - #[argh(option, short = 'l', default = "Path::new(\".\").into()")] - pub lib_path: PathBuf, - - /// output file - #[argh(option, short = 'o', default = "OutputFile::Stdout")] - pub output: OutputFile, -} - -fn read_path(path: &str) -> Result { - Ok(Path::new(path).into()) -} - -#[derive(Default)] -pub struct ComponentCellsBackend; - -fn main() -> CalyxResult<()> { - let p: Args = argh::from_env(); - - let ws = frontend::Workspace::construct(&p.file_path, &p.lib_path)?; - - let ctx: ir::Context = ir::from_ast::ast_to_ir(ws)?; - - let main_comp = ctx.entrypoint(); - - let mut component_info: HashSet = HashSet::new(); - gen_component_info(&ctx, main_comp, &mut component_info); - write_json(component_info.clone(), p.output)?; - Ok(()) -} - -fn id_serialize_passthrough(id: &Id, ser: S) -> Result -where - S: serde::Serializer, -{ - id.to_string().serialize(ser) -} - -#[derive(PartialEq, Eq, Hash, Clone, Serialize)] -struct ComponentGroupInfo { - #[serde(serialize_with = "id_serialize_passthrough")] - pub component: Id, - pub groups: Vec, -} - -/// Accumulates a set of components to the cells that they contain -/// in the program with entrypoint `main_comp`. The contained cells -/// are denoted with the name of the cell and the name of the component -/// the cell is associated with. -fn gen_component_info( - ctx: &ir::Context, - comp: &ir::Component, - component_info: &mut HashSet, -) { - let mut curr_comp_info = ComponentGroupInfo { - component: comp.name, - groups: Vec::new(), - }; - for group_wrapped in comp.get_groups() { - curr_comp_info.groups.push(group_wrapped.borrow().name()); - } - for cell in comp.cells.iter() { - let cell_ref = cell.borrow(); - if let ir::CellType::Component { name } = cell_ref.prototype { - let component = ctx - .components - .iter() - .find(|comp| comp.name == name) - .unwrap(); - gen_component_info(ctx, component, component_info); - } - } - component_info.insert(curr_comp_info); -} - -/// Write the collected set of component information to a JSON file. -fn write_json( - component_info: HashSet, - file: OutputFile, -) -> Result<(), io::Error> { - let created_vec: Vec = - component_info.into_iter().collect(); - serde_json::to_writer_pretty(file.get_write(), &created_vec)?; - Ok(()) -} diff --git a/tools/profiler/convert-dump.py b/tools/profiler/convert-dump.py index 8cab93565..04a88e7b8 100644 --- a/tools/profiler/convert-dump.py +++ b/tools/profiler/convert-dump.py @@ -9,12 +9,15 @@ def main(profiler_dump_file, out_file): profiled_info = json.load(open(profiler_dump_file, "r")) - cat = "C" # Category... might do something with this later + cat = "GT" # Ground truth category (will overwrite if it's FSM) events = [] id_acc = 1 ts_multiplier = 100 # some arbitrary number to multiply by so that it's easier to see in the viewer for group_info in profiled_info: name = group_info["name"].split("TOP.toplevel.", 1)[1] + if group_info["fsm_name"] is not None: + cat = "FSM" + name = "[FSM] " + name for segment in group_info["closed_segments"]: # beginning of segment begin_time = segment["start"] * ts_multiplier diff --git a/tools/profiler/get-profile-counts-info.sh b/tools/profiler/get-profile-counts-info.sh index f2d3cc75c..e796c8e83 100644 --- a/tools/profiler/get-profile-counts-info.sh +++ b/tools/profiler/get-profile-counts-info.sh @@ -8,16 +8,16 @@ fi SCRIPT_DIR=$( cd $( dirname $0 ) && pwd ) SCRIPT_NAME=$( echo "$0" | rev | cut -d/ -f1 | rev ) CALYX_DIR=$( dirname $( dirname ${SCRIPT_DIR} ) ) -name=$( echo "${INPUT_FILE}" | rev | cut -d/ -f1 | rev | cut -d. -f1 ) -DATA_DIR=${SCRIPT_DIR}/data/${name} -TMP_DIR=${DATA_DIR}/tmp INPUT_FILE=$1 SIM_DATA_JSON=$2 -if [[ $# -ge 3 ]]; then - OUT_CSV=$3 +name=$( echo "${INPUT_FILE}" | rev | cut -d/ -f1 | rev | cut -d. -f1 ) +DATA_DIR=${SCRIPT_DIR}/data/${name} +TMP_DIR=${DATA_DIR}/tmp +if [ $# -ge 3 ]; then + OUT_CSV=$3 else - OUT_CSV=${TMP_DIR}/summary.csv + OUT_CSV=${TMP_DIR}/summary.csv fi TMP_VERILOG=${TMP_DIR}/no-opt-verilog.sv @@ -66,12 +66,12 @@ echo "[${SCRIPT_NAME}] Using FSM info and VCD file to obtain cycle level counts" ) &> ${LOGS_DIR}/gol-process if [ "$4" == "-d" ]; then - cat ${LOGS_DIR}/gol-process + cat ${LOGS_DIR}/gol-process | grep -v Writing else tail -2 ${LOGS_DIR}/gol-process fi -echo "[${SCRIPT_NAME}] Writing visualization to ${VISUALS_JSON}" +echo "[${SCRIPT_NAME}] Writing visualization" ( python3 ${SCRIPT_DIR}/convert-dump.py ${OUT_JSON} ${VISUALS_JSON} ) &> ${LOGS_DIR}/gol-visuals diff --git a/tools/profiler/parse-vcd.py b/tools/profiler/parse-vcd.py index c95c9bd5d..12f3f7ec7 100644 --- a/tools/profiler/parse-vcd.py +++ b/tools/profiler/parse-vcd.py @@ -23,9 +23,15 @@ def __repr__ (self): if (segments_str != ""): segments_str += ", " segments_str += f"[{segment['start']}, {segment['end']})" - return (f"Group {self.name}:\n" + - f"\tFSM name: {self.fsm_name}\n" + - f"\tFSM state ids: {self.fsm_values}\n" + + if self.fsm_name is None: + header = f"[GT] Group {self.name}:\n" + else: + header = (f"[FSM] Group {self.name}:\n" + + f"\tFSM name: {self.fsm_name}\n" + + f"\tFSM state ids: {self.fsm_values}\n" + ) + + return (header + f"\tTotal cycles: {self.total_cycles}\n" + f"\t# of times active: {len(self.closed_segments)}\n" + f"\tSegments: {segments_str}\n" @@ -47,13 +53,20 @@ def compute_average_cycles(self): return round(self.total_cycles / len(self.closed_segments), 2) def emit_csv_data(self): - return {"name": self.name, + name = self.name + if self.fsm_name is not None: + name += "[FSM]" + return {"name": name, "total-cycles" : self.total_cycles, "times-active" : len(self.closed_segments), "avg" : self.compute_average_cycles()} def summary(self): - return (f"Group {self.name} Summary:\n" + + if self.fsm_name is None: + header = "[GT] " + else: + header = "[FSM]" + return (f"{header} Group {self.name} Summary:\n" + f"\tTotal cycles: {self.total_cycles}\n" + f"\t# of times active: {len(self.closed_segments)}\n" + f"\tAvg runtime: {self.compute_average_cycles()}\n" @@ -76,58 +89,65 @@ def end_current_segment(self, curr_clock_cycle): class VCDConverter(vcdvcd.StreamParserCallbacks): - def __init__(self, fsms, single_enable_names, tdcc_group_names, fsm_group_maps, cells_to_components, main_component): + def __init__(self, fsms, single_enable_names, tdcc_groups, fsm_group_maps, main_component): super().__init__() self.main_component = main_component self.fsms = fsms self.single_enable_names = single_enable_names - self.cells_to_components = cells_to_components # Recording the first cycle when the TDCC group became active - self.tdcc_group_active_cycle = {tdcc_group_name : -1 for tdcc_group_name in tdcc_group_names} - self.tdcc_group_to_go_id = {tdcc_group_name : None for tdcc_group_name in tdcc_group_names} + self.tdcc_group_active_cycle = {tdcc_group_name : -1 for tdcc_group_name in tdcc_groups} + # Map from a TDCC group to all FSMs that depend on it. maybe a 1:1 mapping + self.tdcc_group_to_dep_fsms = tdcc_groups + # Group name --> ProfilingInfo object self.profiling_info = {} - self.signal_to_signal_id = {fsm : None for fsm in fsms} - self.signal_to_curr_value = {fsm : 0 for fsm in fsms} - self.main_go_id = None - self.main_go_on = False - self.main_go_on_time = None - self.clock_id = None - self.clock_cycle_acc = -1 # The 0th clock cycle will be 0. + self.signal_to_curr_value = {fsm : -1 for fsm in fsms} for group in fsm_group_maps: - self.profiling_info[group] = ProfilingInfo(group, fsm_group_maps[group]["fsm"], fsm_group_maps[group]["ids"], fsm_group_maps[group]["tdcc-group-name"]) + # Differentiate FSM versions from ground truth versions + self.profiling_info[f"{group}FSM"] = ProfilingInfo(group, fsm_group_maps[group]["fsm"], fsm_group_maps[group]["ids"], fsm_group_maps[group]["tdcc-group-name"]) for single_enable_group in single_enable_names: self.profiling_info[single_enable_group] = ProfilingInfo(single_enable_group) self.signal_to_curr_value[f"{single_enable_group}_go"] = -1 self.signal_to_curr_value[f"{single_enable_group}_done"] = -1 + # Map from timestamps [ns] to value change events that happened on that timestamp + self.timestamps_to_events = {} def enddefinitions(self, vcd, signals, cur_sig_vals): # convert references to list and sort by name refs = [(k, v) for k, v in vcd.references_to_ids.items()] refs = sorted(refs, key=lambda e: e[0]) names = [remove_size_from_name(e[0]) for e in refs] - self.main_go_id = vcd.references_to_ids[f"{self.main_component}.go"] + signal_id_dict = {sid : [] for sid in vcd.references_to_ids.values()} # one id can map to multiple signal names since wires are connected + main_go_name = f"{self.main_component}.go" + signal_id_dict[vcd.references_to_ids[main_go_name]] = [main_go_name] clock_name = f"{self.main_component}.clk" - if clock_name in names: - self.clock_id = vcd.references_to_ids[clock_name] - else: + if clock_name not in names: print("Can't find the clock? Exiting...") sys.exit(1) + signal_id_dict[vcd.references_to_ids[clock_name]] = [clock_name] - for name, id in refs: - # We may want to optimize these nested for loops - for tdcc_group in self.tdcc_group_to_go_id: + for name, sid in refs: + # FIXME: We may want to optimize these nested for loops + for tdcc_group in self.tdcc_group_to_dep_fsms: if name.startswith(f"{tdcc_group}_go.out["): - self.tdcc_group_to_go_id[tdcc_group] = id + signal_id_dict[sid].append(name) for fsm in self.fsms: if name.startswith(f"{fsm}.out["): - self.signal_to_signal_id[fsm] = id + signal_id_dict[sid].append(name) for single_enable_group in self.single_enable_names: if name.startswith(f"{single_enable_group}_go.out["): - self.signal_to_signal_id[f"{single_enable_group}_go"] = id + signal_id_dict[sid].append(name) if name.startswith(f"{single_enable_group}_done.out["): - self.signal_to_signal_id[f"{single_enable_group}_done"] = id + signal_id_dict[sid].append(name) + # don't need to check for signal ids that don't pertain to signals we're interested in + self.signal_id_to_names = {k:v for k,v in signal_id_dict.items() if len(v) > 0} + + # Stream processes the events recorded in the VCD and stores them in self.timestamps_to_events + # NOTE: Stream processing doesn't work because value changes that happen in the same timestamp + # are not processed at the same time. + # NOTE: when we reimplement this script, we probably want to separate this part from the + # clock cycle processing def value( self, vcd, @@ -136,63 +156,96 @@ def value( identifier_code, cur_sig_vals, ): - # Start profiling after main's go is on - if identifier_code == self.main_go_id and value == "1": - self.main_go_on_time = time - if self.main_go_on_time is None : + # ignore all signals we don't care about + if identifier_code not in self.signal_id_to_names: return + + signal_names = self.signal_id_to_names[identifier_code] + int_value = int(value, 2) + + if time not in self.timestamps_to_events: + self.timestamps_to_events[time] = [] - # detect rising edge on clock - if identifier_code == self.clock_id and value == "1": - self.clock_cycle_acc += 1 - # Update TDCC group signals first - for (tdcc_group_name, tdcc_signal_id) in self.tdcc_group_to_go_id.items(): - tdcc_group_is_active = int(cur_sig_vals[tdcc_signal_id], 2) == 1 - if self.tdcc_group_active_cycle[tdcc_group_name] == -1 and tdcc_group_is_active: # the tdcc group just became active - self.tdcc_group_active_cycle[tdcc_group_name] = self.clock_cycle_acc - elif self.tdcc_group_active_cycle[tdcc_group_name] > -1 and not tdcc_group_is_active: - self.tdcc_group_active_cycle[tdcc_group_name] = -1 - # for each signal that we want to check, we need to sample the values - for (signal_name, signal_id) in self.signal_to_signal_id.items(): - signal_curr_value = self.signal_to_curr_value[signal_name] - signal_new_value = int(cur_sig_vals[signal_id], 2) # signal value at this point in time - if "_go" in signal_name and signal_new_value == 1: - # start of group ground truth + for signal_name in signal_names: + event = {"signal": signal_name, "value": int_value} + self.timestamps_to_events[time].append(event) + + # Postprocess data mapping timestamps to events (signal changes) + # We have to postprocess instead of processing signals in a stream because + # signal changes that happen at the same time as a clock tick might be recorded + # *before* or *after* the clock change on the VCD file (hence why we can't process + # everything within a stream if we wanted to be precise) + def postprocess(self): + clock_name = f"{self.main_component}.clk" + clock_cycles = -1 + fsm_to_active_group = {fsm : None for fsm in self.fsms} + # current values of FSM registers. This is different from fsm_to_active_group since the TDCC group for the FSM + # may not be active (which means that no group managed by the FSM is active) + fsm_to_curr_value = {fsm: -1 for fsm in self.fsms} + started = False + for ts in self.timestamps_to_events: + events = self.timestamps_to_events[ts] + started = started or [x for x in events if x["signal"] == f"{self.main_component}.go" and x["value"] == 1] + if not started: + # Update fsm_to_curr_value for any FSM signals that got updated. We will start the events corresponding + # to those values once the TDCC group for the FSM starts. + # Realistically this will most likely only happen on the 0th cycle just to set the FSM value to 0, + # but trying to be extra safe here. + for event in filter(lambda e : "fsm" in e["signal"], events): + fsm = ".".join(event["signal"].split(".")[0:-1]) + if event["value"] in self.fsms[fsm]: + fsm_to_curr_value[fsm] = event["value"] + continue + # checking whether the timestamp has a rising edge (hacky) + if {"signal": clock_name, "value": 1} in events: + clock_cycles += 1 + # TDCC groups need to be recorded for tracking FSM values + # (ex. if the FSM has value 0 but the TDCC group isn't active, then the group represented by the + # FSM's 0 value should not be considered as active) + for tdcc_event in filter(lambda e : "tdcc" in e["signal"] and "go" in e["signal"], events): + tdcc_group = "_".join(tdcc_event["signal"].split("_")[0:-1]) + if self.tdcc_group_active_cycle[tdcc_group] == -1 and tdcc_event["value"] == 1: # value changed to 1 + self.tdcc_group_active_cycle[tdcc_group] = clock_cycles + for fsm in self.tdcc_group_to_dep_fsms[tdcc_group]: + value = fsm_to_curr_value[fsm] + if value != -1: + next_group = f"{self.fsms[fsm][value]}FSM" + fsm_to_active_group[fsm] = next_group + self.profiling_info[next_group].start_new_segment(clock_cycles) + elif self.tdcc_group_active_cycle[tdcc_group] > -1 and tdcc_event["value"] == 0: # tdcc group that was active's signal turned to 0 + self.tdcc_group_active_cycle[tdcc_group] = -1 + for event in events: + signal_name = event["signal"] + value = event["value"] + if "tdcc" in signal_name and "go" in signal_name: # skip all tdcc events since we've already processed them + continue + if "_go" in signal_name and value == 1: group = "_".join(signal_name.split("_")[0:-1]) - curr_group_info = self.profiling_info[group] - # We want to start a segment regardless of whether it changed - if self.main_go_on_time == time or signal_new_value != signal_curr_value: - curr_group_info.start_new_segment(self.clock_cycle_acc) - elif "_done" in signal_name and signal_new_value == 1: - # end of single enable group + self.profiling_info[group].start_new_segment(clock_cycles) + elif "_done" in signal_name and value == 1: group = "_".join(signal_name.split("_")[0:-1]) - self.profiling_info[group].end_current_segment(self.clock_cycle_acc) + self.profiling_info[group].end_current_segment(clock_cycles) elif "fsm" in signal_name: + fsm = ".".join(signal_name.split(".")[0:-1]) + fsm_to_curr_value[fsm] = value # Workarounds because the value 0 may not correspond to a group - if signal_curr_value in self.fsms[signal_name]: - # group that is recorded to be active last cycle. If the signal changed then it would be the previous group - curr_group = self.fsms[signal_name][signal_curr_value] - # if the FSM value changed, then we must end the current group (regardless of whether we can start the next group) - if signal_new_value != signal_curr_value and signal_curr_value != -1: - self.profiling_info[curr_group].end_current_segment(self.clock_cycle_acc) - if signal_new_value in self.fsms[signal_name]: - next_group = self.fsms[signal_name][signal_new_value] + if fsm_to_active_group[fsm] is not None: + prev_group = fsm_to_active_group[fsm] # getting the "FSM" variant of the group + self.profiling_info[prev_group].end_current_segment(clock_cycles) + if value in self.fsms[fsm]: + next_group = f"{self.fsms[fsm][value]}FSM" # getting the "FSM" variant of the group tdcc_group_active_cycle = self.tdcc_group_active_cycle[self.profiling_info[next_group].tdcc_group] if tdcc_group_active_cycle == -1: # If the TDCC group is not active, then no segments should start continue - # if the FSM value didn't change but the TDCC group just got enabled, then we must start the next group - if signal_new_value == signal_curr_value and tdcc_group_active_cycle == self.clock_cycle_acc: - self.profiling_info[next_group].start_new_segment(self.clock_cycle_acc) - elif signal_new_value != signal_curr_value: # otherwise we start a new segment when the signal changed - self.profiling_info[next_group].start_new_segment(self.clock_cycle_acc) - # Update internal signal value - self.signal_to_curr_value[signal_name] = signal_new_value + fsm_to_active_group[fsm] = next_group + self.profiling_info[next_group].start_new_segment(clock_cycles) + + self.clock_cycles = clock_cycles # Generates a list of all of the components to potential cell names -# prefix is the cell's "path" (ex. for a cell "my_cell" defined in "main", the prefix would be "TOP.toplevel.main") +# `prefix` is the cell's "path" (ex. for a cell "my_cell" defined in "main", the prefix would be "TOP.toplevel.main") # The initial value of curr_component should be the top level/main component def build_components_to_cells(prefix, curr_component, cells_to_components, components_to_cells): - # prefix += f".{curr_component}" for (cell, cell_component) in cells_to_components[curr_component].items(): if cell_component not in components_to_cells: components_to_cells[cell_component] = [f"{prefix}.{cell}"] @@ -220,11 +273,11 @@ def read_component_cell_names_json(json_file): build_components_to_cells(full_main_component, main_component, cells_to_components, components_to_cells) return full_main_component, components_to_cells -# Reads json generated by TDCC (via dump-fsm-json option) to produce +# Reads json generated by TDCC (via dump-fsm-json option) to produce initial group information def remap_tdcc_json(json_file, components_to_cells): profiling_infos = json.load(open(json_file)) - single_enable_names = set() # groups that aren't managed by FSMs - tdcc_group_names = set() # TDCC-generated groups that manage control flow using FSMs + group_names = set() # all groups (to record ground truth) + tdcc_groups = {} # TDCC-generated groups that manage control flow using FSMs. maps to all fsms that map to the tdcc group fsm_group_maps = {} # fsm-managed groups info (fsm register, TDCC group that manages fsm, id of group within fsm) fsms = {} # Remapping of JSON data for easy access for profiling_info in profiling_infos: @@ -240,21 +293,20 @@ def remap_tdcc_json(json_file, components_to_cells): tdcc_group = cell + "." + fsm["group"] if group_name not in fsm_group_maps: fsm_group_maps[group_name] = {"fsm": fsm_name, "tdcc-group-name": tdcc_group, "ids": [state["id"]]} - tdcc_group_names.add(tdcc_group) # Keep track of the TDCC group to figure out when first group starts + if tdcc_group not in tdcc_groups: # Keep track of the TDCC group to figure out when first group starts + tdcc_groups[tdcc_group] = set() + tdcc_groups[tdcc_group].add(fsm_name) + group_names.add(group_name) else: fsm_group_maps[group_name]["ids"].append(state["id"]) else: for cell in components_to_cells[profiling_info["SingleEnable"]["component"]]: # get all possibilities of cells - single_enable_names.add(cell + "." + profiling_info["SingleEnable"]["group"]) + group_names.add(cell + "." + profiling_info["SingleEnable"]["group"]) - return fsms, single_enable_names, tdcc_group_names, fsm_group_maps + return fsms, group_names, tdcc_groups, fsm_group_maps -def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json): - main_component, components_to_cells = read_component_cell_names_json(cells_json_file) - fsms, single_enable_names, tdcc_group_names, fsm_group_maps = remap_tdcc_json(groups_json_file, components_to_cells) - converter = VCDConverter(fsms, single_enable_names, tdcc_group_names, fsm_group_maps, components_to_cells, main_component) - vcdvcd.VCDVCD(vcd_filename, callbacks=converter, store_tvs=False) - print(f"Total clock cycles: {converter.clock_cycle_acc}") +def output_result(out_csv, dump_out_json, converter): + print(f"Total clock cycles: {converter.clock_cycles}") print("=====SUMMARY=====") print() groups_to_emit = list(filter(lambda group : not group.name.startswith("tdcc") and not group.name.endswith("END"), converter.profiling_info.values())) @@ -277,7 +329,7 @@ def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json # emitting a CSV file for easier eyeballing print(f"Writing summary to {out_csv}") csv_keys = ["name", "total-cycles", "times-active", "avg"] - csv_acc.append({ "name": "TOTAL", "total-cycles": converter.clock_cycle_acc, "times-active": "-", "avg": "-"}) + csv_acc.append({ "name": "TOTAL", "total-cycles": converter.clock_cycles, "times-active": "-", "avg": "-"}) if (out_csv == "STDOUT"): writer = csv.DictWriter(sys.stdout, csv_keys, lineterminator="\n") else: @@ -285,6 +337,14 @@ def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json writer.writeheader() writer.writerows(csv_acc) +def main(vcd_filename, groups_json_file, cells_json_file, out_csv, dump_out_json): + main_component, components_to_cells = read_component_cell_names_json(cells_json_file) + fsms, group_names, tdcc_group_names, fsm_group_maps = remap_tdcc_json(groups_json_file, components_to_cells) + converter = VCDConverter(fsms, group_names, tdcc_group_names, fsm_group_maps, main_component) + vcdvcd.VCDVCD(vcd_filename, callbacks=converter, store_tvs=False) + converter.postprocess() + output_result(out_csv, dump_out_json, converter) + if __name__ == "__main__": if len(sys.argv) > 5: vcd_filename = sys.argv[1]