Skip to content

Commit

Permalink
[Profiling] Sample values at clock falling edges and accommodate sing…
Browse files Browse the repository at this point in the history
…le-enable control blocks/par arms (#2081)

* [WIP] Saving scratch work. Most likely want to switch to VCDVCD streaming like Kevin's example

* Very rough first pass at sampling FSM values

* Some cleanup

* Add start and end times for each group

* Small bug fix

* Add documentation

* Get TDCC to emit different information for parallel arms that only have one group

* Finagle with documentation

* [WIP] Setting up signal ID mapping from signal names

* [WIP] Read single value groups. Need to fix the FSMs

* Add FSM entry for "END"

* Fixed up most of fsm0 (language-tutorial-iterate) profiling.

* Emit JSON when the control only consists of one group

* Remove resolved TODO

* Documentation and struct name changes

* Fix bug where FSM values of main's startup time didn't get profiled

* Rename tools/vcd-parsing to tools/profiler

* Fix up documentation
  • Loading branch information
ayakayorihiro authored Jun 4, 2024
1 parent a2d9594 commit 3565623
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 65 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ results.xml
tools/btor2/btor2i/build/

# vcd-parsing tmp files ignore
tools/vcd-parsing/tmp/
tools/vcd-parsing/logs
tools/profiler/tmp/
tools/profiler/logs
84 changes: 68 additions & 16 deletions calyx-opt/src/passes/top_down_compile_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,13 +222,32 @@ struct Schedule<'b, 'a: 'b> {
pub builder: &'b mut ir::Builder<'a>,
}

/// Information to serialize for profiling purposes
#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
enum ProfilingInfo {
Fsm(FSMInfo),
SingleEnable(SingleEnableInfo),
}

/// Information to be serialized for a group that isn't managed by a FSM
/// This can happen if the group is the only group in a control block or a par arm
#[derive(PartialEq, Eq, Hash, Clone, Serialize)]
struct SingleEnableInfo {
#[serde(serialize_with = "id_serialize_passthrough")]
pub component: Id,
#[serde(serialize_with = "id_serialize_passthrough")]
pub group: Id,
}

/// 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,
#[serde(serialize_with = "id_serialize_passthrough")]
pub fsm: Id,
pub states: Vec<FSMStateInfo>,
}

Expand Down Expand Up @@ -311,7 +330,7 @@ impl<'b, 'a> Schedule<'b, 'a> {
fn realize_schedule(
self,
dump_fsm: bool,
fsm_groups: &mut HashSet<FSMInfo>,
fsm_groups: &mut HashSet<ProfilingInfo>,
) -> RRC<ir::Group> {
self.validate();

Expand All @@ -324,13 +343,6 @@ 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 */
Expand All @@ -342,6 +354,21 @@ impl<'b, 'a> Schedule<'b, 'a> {
let first_state = constant(0, fsm_size);
);

// Add last state to JSON info
let mut states = self.groups_to_states.iter().cloned().collect_vec();
states.push(FSMStateInfo {
id: final_state,
group: Id::new(format!("{}_END", fsm.borrow().name())),
});

// Keep track of groups to FSM state id information for dumping to json
fsm_groups.insert(ProfilingInfo::Fsm(FSMInfo {
component: self.builder.component.name,
fsm: fsm.borrow().name(),
group: group.borrow().name(),
states,
}));

// Enable assignments
group.borrow_mut().assignments.extend(
self.enables
Expand Down Expand Up @@ -437,7 +464,6 @@ 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.
Expand Down Expand Up @@ -819,7 +845,7 @@ pub struct TopDownCompileControl {
/// Enable early transitions
early_transitions: bool,
/// Bookkeeping for FSM ids for groups across all FSMs in the program
fsm_groups: HashSet<FSMInfo>,
fsm_groups: HashSet<ProfilingInfo>,
}

impl ConstructVisitor for TopDownCompileControl {
Expand Down Expand Up @@ -875,22 +901,42 @@ impl Named for TopDownCompileControl {
}
}

/// Helper function to emit profiling information when the control consists of a single group.
fn emit_single_enable(
con: &mut ir::Control,
component: Id,
json_out_file: &OutputFile,
) {
if let ir::Control::Enable(enable) = con {
let mut profiling_info_set: HashSet<ProfilingInfo> = HashSet::new();
profiling_info_set.insert(ProfilingInfo::SingleEnable(
SingleEnableInfo {
component,
group: enable.group.borrow().name(),
},
));
let _ = serde_json::to_writer_pretty(
json_out_file.get_write(),
&profiling_info_set,
);
}
}

impl Visitor for TopDownCompileControl {
fn start(
&mut self,
comp: &mut ir::Component,
_sigs: &LibrarySignatures,
_comps: &[ir::Component],
) -> VisResult {
// Do not try to compile an enable
if matches!(
*comp.control.borrow(),
ir::Control::Enable(..) | ir::Control::Empty(..)
) {
let mut con = comp.control.borrow_mut();
if matches!(*con, ir::Control::Empty(..) | ir::Control::Enable(..)) {
if let Some(json_out_file) = &self.dump_fsm_json {
emit_single_enable(&mut con, comp.name, json_out_file);
}
return Ok(Action::Stop);
}

let mut con = comp.control.borrow_mut();
compute_unique_ids(&mut con, 0);
// IRPrinter::write_control(&con, 0, &mut std::io::stderr());
Ok(Action::Continue)
Expand Down Expand Up @@ -1002,6 +1048,12 @@ impl Visitor for TopDownCompileControl {
let group = match con {
// Do not compile enables
ir::Control::Enable(ir::Enable { group, .. }) => {
self.fsm_groups.insert(ProfilingInfo::SingleEnable(
SingleEnableInfo {
group: group.borrow().name(),
component: builder.component.name,
},
));
Rc::clone(group)
}
// Compile complex schedule and return the group.
Expand Down
File renamed without changes.
File renamed without changes.
172 changes: 172 additions & 0 deletions tools/profiler/parse-vcd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import sys
import json
import vcdvcd

def remove_size_from_name(name: str) -> str:
""" changes e.g. "state[2:0]" to "state" """
return name.split('[')[0]

class ProfilingInfo:
def __init__(self, name, fsm_name=None, fsm_value=None):
self.name = name
self.fsm_name = fsm_name
self.fsm_value = fsm_value
self.total_cycles = 0
self.segments = [] # Segments will be (start_time, end_time)

def __repr__ (self):
# Remove any non-closed segments
segment_repr = []
for segment in self.segments:
if segment["end"] != -1:
segment_repr.append(segment)
return str({"group-name" : self.name, "fsm-name": self.fsm_name, "group-fsm-value": self.fsm_value, "total-cycles": self.total_cycles, "segments": segment_repr})

def start_new_segment(self, curr_clock_cycle):
self.segments.append({"start": curr_clock_cycle, "end": -1})

def end_current_segment(self, curr_clock_cycle):
if len(self.segments) > 0:
# Close out previous segment by setting the end time to the current cycle
if (self.segments[-1]["end"] == -1): # ignore cases where done is high forever
self.segments[-1]["end"] = curr_clock_cycle
self.total_cycles += curr_clock_cycle - self.segments[-1]["start"]

class VCDConverter(vcdvcd.StreamParserCallbacks):

def __init__(self, fsms, single_enable_names, groups_to_fsms):
super().__init__()
self.fsms = fsms
self.single_enable_names = single_enable_names
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.
for group in groups_to_fsms:
fsm_name, fsm_value = groups_to_fsms[group]
self.profiling_info[group] = ProfilingInfo(group, fsm_name, fsm_value)
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

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]

# FIXME: When we get to profiling multi-component programs, we want to search for each component's go signal
self.main_go_id = vcd.references_to_ids["TOP.TOP.main.go"]

clock_name = "TOP.TOP.main.clk"
if clock_name in names:
self.clock_id = vcd.references_to_ids[clock_name]
else:
print("Can't find the clock? Exiting...")
sys.exit(1)

for name, id in refs:
# We may want to optimize these nested for loops
for fsm in self.fsms:
if f"{fsm}.out[" in name:
self.signal_to_signal_id[fsm] = id
for single_enable_group in self.single_enable_names:
if f"{single_enable_group}_go.out[" in name:
self.signal_to_signal_id[f"{single_enable_group}_go"] = id
if f"{single_enable_group}_done.out[" in name:
self.signal_to_signal_id[f"{single_enable_group}_done"] = id

def value(
self,
vcd,
time,
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 :
return

# detect rising edge on clock
if identifier_code == self.clock_id and value == "1":
self.clock_cycle_acc += 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_new_value = int(cur_sig_vals[signal_id], 2) # signal value at this point in time
fsm_curr_value = self.signal_to_curr_value[signal_name]
# skip values that have not changed, except for when main[go] just got activated
if not(self.main_go_on_time == time) and signal_new_value == fsm_curr_value:
continue
if "_go" in signal_name and signal_new_value == 1:
# start of single enable group
group = "_".join(signal_name.split("_")[0:-1])
self.profiling_info[group].start_new_segment(self.clock_cycle_acc)
elif "_done" in signal_name and signal_new_value == 1:
# end of single enable group
group = "_".join(signal_name.split("_")[0:-1])
self.profiling_info[group].end_current_segment(self.clock_cycle_acc)
elif "fsm" in signal_name:
# Sample FSM values
if fsm_curr_value != -1:
# end the previous group if there was one
prev_group = self.fsms[signal_name][fsm_curr_value]
self.profiling_info[prev_group].end_current_segment(self.clock_cycle_acc)
if signal_new_value in self.fsms[signal_name]: # END should be ignored
next_group = self.fsms[signal_name][signal_new_value]
# start a new segment for the next group
# FIXME: need to fix this for par blocks
self.profiling_info[next_group].start_new_segment(self.clock_cycle_acc)
else:
# The state id was not in the JSON entry for this FSM. Most likely the value was the last FSM state.
print(f"FSM value ignored: {signal_new_value}")
# Update internal signal value
self.signal_to_curr_value[signal_name] = signal_new_value

def remap_tdcc_json(json_file):
profiling_infos = json.load(open(json_file))
single_enable_names = set()
groups_to_fsms = {}
fsms = {} # Remapping of JSON data for easy access
for profiling_info in profiling_infos:
if "Fsm" in profiling_info:
fsm = profiling_info["Fsm"]
fsm_name = fsm["fsm"]
fsms[fsm_name] = {}
for state in fsm["states"]:
fsms[fsm_name][state["id"]] = state["group"]
groups_to_fsms[state["group"]] = (fsm_name, state["id"])
else:
group_name = profiling_info["SingleEnable"]["group"]
single_enable_names.add(group_name)

return fsms, single_enable_names, groups_to_fsms


def main(vcd_filename, json_file):
fsms, single_enable_names, groups_to_fsms = remap_tdcc_json(json_file)
converter = VCDConverter(fsms, single_enable_names, groups_to_fsms)
vcdvcd.VCDVCD(vcd_filename, callbacks=converter, store_tvs=False)
print(f"Total clock cycles: {converter.clock_cycle_acc}")
for group_info in converter.profiling_info.values():
print(group_info)

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)
47 changes: 0 additions & 47 deletions tools/vcd-parsing/parse-vcd.py

This file was deleted.

0 comments on commit 3565623

Please sign in to comment.