calyx_opt/passes/
profiler_instrumentation.rs

1use core::panic;
2use std::collections::{HashMap, HashSet};
3
4use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor};
5use calyx_ir::{self as ir, BoolAttr, Guard, Id, Nothing, NumAttr};
6use calyx_utils::CalyxResult;
7
8/// Adds probe wires to each group (includes static groups and comb groups) to detect when a group is active.
9/// Used by the profiler.
10pub struct ProfilerInstrumentation {}
11
12/// Mapping group names to constructs (groups/primitives/cells) that the group enabled,
13/// along with the guard that was involved in the assignment.
14type CallsFromGroupMap<T> = HashMap<Id, Vec<(Id, ir::Guard<T>)>>;
15
16impl Named for ProfilerInstrumentation {
17    fn name() -> &'static str {
18        "profiler-instrumentation"
19    }
20
21    fn description() -> &'static str {
22        "Add instrumentation for profiling"
23    }
24
25    fn opts() -> Vec<crate::traversal::PassOpt> {
26        vec![]
27    }
28}
29
30impl ConstructVisitor for ProfilerInstrumentation {
31    fn from(_ctx: &ir::Context) -> CalyxResult<Self>
32    where
33        Self: Sized + Named,
34    {
35        Ok(ProfilerInstrumentation {})
36    }
37
38    fn clear_data(&mut self) {}
39}
40
41/// Creates probe cells and assignments pertaining to standard groups.
42fn group(comp: &mut ir::Component, sigs: &ir::LibrarySignatures) {
43    // groups to groups that they enabled
44    let mut structural_enable_map: CallsFromGroupMap<Nothing> = HashMap::new();
45    // groups to cells (from non-primitive components) that they invoked
46    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
47    // groups to primitives that they invoked
48    let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
49    let group_names = comp
50        .groups
51        .iter()
52        .map(|group| group.borrow().name())
53        .collect::<Vec<_>>();
54
55    // Dynamic groups: iterate and check for structural enables, cell invokes, and primitive enables
56    for group_ref in comp.groups.iter() {
57        let group = &group_ref.borrow();
58        // set to prevent adding multiple probes for a combinational primitive enabled by the group
59        let mut comb_primitives_covered = HashSet::new();
60        let mut primitive_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
61        for assignment_ref in group.assignments.iter() {
62            let dst_borrow = assignment_ref.dst.borrow();
63            if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
64            {
65                if dst_borrow.name == "go" {
66                    // found an invocation of go
67                    let invoked_group_name =
68                        parent_group_ref.upgrade().borrow().name();
69                    let guard = *(assignment_ref.guard.clone());
70                    match structural_enable_map.get_mut(&invoked_group_name) {
71                        Some(vec_ref) => vec_ref.push((group.name(), guard)),
72                        None => {
73                            structural_enable_map.insert(
74                                invoked_group_name,
75                                vec![(group.name(), guard)],
76                            );
77                        }
78                    }
79                }
80            }
81            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
82                match cell_ref.upgrade().borrow().prototype.clone() {
83                    calyx_ir::CellType::Primitive {
84                        name: _,
85                        param_binding: _,
86                        is_comb,
87                        latency: _,
88                    } => {
89                        let cell_name = cell_ref.upgrade().borrow().name();
90                        if is_comb {
91                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
92                            if comb_primitives_covered.insert(cell_name) {
93                                primitive_vec.push((cell_name, Guard::True));
94                            }
95                        } else if dst_borrow.has_attribute(NumAttr::Go) {
96                            // non-combinational primitives
97                            let guard = Guard::and(
98                                *(assignment_ref.guard.clone()),
99                                Guard::port(ir::rrc(
100                                    assignment_ref.src.borrow().clone(),
101                                )),
102                            );
103                            primitive_vec.push((cell_name, guard));
104                        }
105                    }
106                    calyx_ir::CellType::Component { name: _ } => {
107                        if dst_borrow.has_attribute(NumAttr::Go) {
108                            let cell_name = cell_ref.upgrade().borrow().name();
109                            let guard = *(assignment_ref.guard.clone());
110                            match cell_invoke_map.get_mut(&group.name()) {
111                                Some(vec_ref) => {
112                                    vec_ref.push((cell_name, guard));
113                                }
114                                None => {
115                                    cell_invoke_map.insert(
116                                        group.name(),
117                                        vec![(cell_name, guard)],
118                                    );
119                                }
120                            }
121                        }
122                    }
123                    _ => (),
124                }
125            }
126        }
127        primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
128    }
129
130    // create probe cells and assignments
131    let group_name_assign_and_cell = create_assignments(
132        comp,
133        sigs,
134        &group_names,
135        Some(structural_enable_map),
136        Some(cell_invoke_map),
137        Some(primitive_invoke_map),
138    );
139
140    // Add created assignments to each group and their corresponding probe cells
141    for group in comp.groups.iter() {
142        for (group_name, asgn, cell) in group_name_assign_and_cell.iter() {
143            if group.borrow().name() == group_name {
144                group.borrow_mut().assignments.push(asgn.clone());
145                comp.cells.add(cell.to_owned());
146            }
147        }
148    }
149}
150
151/// Creates probe cells and assignments pertaining to combinational groups.
152fn combinational_group(comp: &mut ir::Component, sigs: &ir::LibrarySignatures) {
153    // NOTE: combinational groups cannot structurally enable other groups
154
155    // groups to cells (from non-primitive components) that they invoked
156    let mut cell_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
157    // groups to primitives that they invoked
158    let mut primitive_invoke_map: CallsFromGroupMap<Nothing> = HashMap::new();
159
160    let group_names = comp
161        .comb_groups
162        .iter()
163        .map(|group| group.borrow().name())
164        .collect::<Vec<_>>();
165
166    for group_ref in comp.comb_groups.iter() {
167        let group = &group_ref.borrow();
168        let mut comb_primitives_covered = HashSet::new();
169        let mut comb_cells_covered = HashSet::new();
170
171        for assignment_ref in group.assignments.iter() {
172            let dst_borrow = assignment_ref.dst.borrow();
173            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
174                match cell_ref.upgrade().borrow().prototype.clone() {
175                    calyx_ir::CellType::Primitive {
176                        name: _,
177                        param_binding: _,
178                        is_comb,
179                        latency: _,
180                    } => {
181                        let cell_name = cell_ref.upgrade().borrow().name();
182                        if is_comb {
183                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
184                            if comb_primitives_covered.insert(cell_name) {
185                                match primitive_invoke_map
186                                    .get_mut(&group.name())
187                                {
188                                    Some(vec_ref) => {
189                                        vec_ref.push((cell_name, Guard::True));
190                                    }
191                                    None => {
192                                        primitive_invoke_map.insert(
193                                            group.name(),
194                                            vec![(cell_name, Guard::True)],
195                                        );
196                                    }
197                                }
198                            }
199                        } else if dst_borrow.has_attribute(NumAttr::Go) {
200                            panic!(
201                                "Non-combinational primitive {} invoked inside of combinational group {}!",
202                                dst_borrow.canonical(),
203                                group.name()
204                            )
205                        }
206                    }
207                    calyx_ir::CellType::Component { name: _ } => {
208                        let cell_name = cell_ref.upgrade().borrow().name();
209                        if dst_borrow.name == "go" {
210                            panic!(
211                                "Non-combinational cell {} invoked inside of combinational group {}!",
212                                cell_name,
213                                group.name()
214                            );
215                        } else if comb_cells_covered.insert(cell_name) {
216                            let guard = *(assignment_ref.guard.clone());
217                            match cell_invoke_map.get_mut(&group.name()) {
218                                Some(vec_ref) => {
219                                    vec_ref.push((cell_name, guard));
220                                }
221                                None => {
222                                    cell_invoke_map.insert(
223                                        group.name(),
224                                        vec![(cell_name, guard)],
225                                    );
226                                }
227                            }
228                        }
229                    }
230                    _ => (),
231                }
232            }
233        }
234    }
235
236    let group_name_asgn_and_cell = create_assignments(
237        comp,
238        sigs,
239        &group_names,
240        None, // assuming no structural enables within comb groups
241        Some(cell_invoke_map),
242        Some(primitive_invoke_map),
243    );
244
245    // Comb: Add created assignments to each group
246    for comb_group in comp.comb_groups.iter() {
247        for (comb_group_name, asgn, cell) in group_name_asgn_and_cell.iter() {
248            if comb_group.borrow().name() == comb_group_name {
249                comb_group.borrow_mut().assignments.push(asgn.clone());
250                comp.cells.add(cell.to_owned());
251            }
252        }
253    }
254}
255
256/// Creates probe cells and assignments pertaining to static groups.
257fn static_group(comp: &mut ir::Component, sigs: &ir::LibrarySignatures) {
258    let group_names = comp
259        .static_groups
260        .iter()
261        .map(|group| group.borrow().name())
262        .collect::<Vec<_>>();
263
264    // groups to groups that they enabled
265    let mut structural_enable_map: CallsFromGroupMap<ir::StaticTiming> =
266        HashMap::new();
267    // groups to cells (from non-primitive components) that they invoked
268    let mut cell_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
269        HashMap::new();
270    // groups to primitives that they invoked
271    let mut primitive_invoke_map: CallsFromGroupMap<ir::StaticTiming> =
272        HashMap::new();
273
274    for group_ref in comp.static_groups.iter() {
275        let group = &group_ref.borrow();
276        // set to prevent adding multiple probes for a combinational primitive enabled by the group
277        let mut comb_primitives_covered = HashSet::new();
278        let mut primitive_vec: Vec<(Id, ir::Guard<ir::StaticTiming>)> =
279            Vec::new();
280        for assignment_ref in group.assignments.iter() {
281            let dst_borrow = assignment_ref.dst.borrow();
282            if let ir::PortParent::Group(parent_group_ref) = &dst_borrow.parent
283            {
284                if dst_borrow.name == "go" {
285                    // found an invocation of go
286                    let invoked_group_name =
287                        parent_group_ref.upgrade().borrow().name();
288                    let guard = *(assignment_ref.guard).clone();
289                    structural_enable_map
290                        .entry(invoked_group_name)
291                        .or_default()
292                        .push((group.name(), guard));
293                }
294            }
295            if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
296                match cell_ref.upgrade().borrow().prototype.clone() {
297                    calyx_ir::CellType::Primitive { is_comb, .. } => {
298                        let cell_name = cell_ref.upgrade().borrow().name();
299                        if is_comb {
300                            // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
301                            if comb_primitives_covered.insert(cell_name) {
302                                primitive_vec.push((cell_name, Guard::True));
303                            }
304                        } else if dst_borrow.has_attribute(NumAttr::Go) {
305                            // non-combinational primitives
306                            let guard = Guard::and(
307                                *(assignment_ref.guard).clone(),
308                                Guard::port(ir::rrc(
309                                    assignment_ref.src.borrow().clone(),
310                                )),
311                            );
312                            primitive_vec.push((cell_name, guard));
313                        }
314                    }
315                    calyx_ir::CellType::Component { name: _ } => {
316                        if dst_borrow.has_attribute(NumAttr::Go) {
317                            let cell_name = cell_ref.upgrade().borrow().name();
318                            let guard = *(assignment_ref.guard.clone());
319                            cell_invoke_map
320                                .entry(group.name())
321                                .or_default()
322                                .push((cell_name, guard));
323                        }
324                    }
325                    _ => (),
326                }
327            }
328        }
329        primitive_invoke_map.insert(group_ref.borrow().name(), primitive_vec);
330    }
331
332    let group_name_assign_and_cell = create_assignments(
333        comp,
334        sigs,
335        &group_names,
336        Some(structural_enable_map),
337        Some(cell_invoke_map),
338        Some(primitive_invoke_map),
339    );
340
341    // Add created assignments to each group
342    for static_group in comp.static_groups.iter() {
343        for (static_group_name, asgn, cell) in group_name_assign_and_cell.iter()
344        {
345            if static_group.borrow().name() == static_group_name {
346                static_group.borrow_mut().assignments.push(asgn.clone());
347                comp.cells.add(cell.to_owned());
348            }
349        }
350    }
351}
352
353/// Creates all probe cells and assignments for a certain kind of .
354/// Returns a Vec where each element is (GROUP, ASGN, CELL) where
355/// GROUP is the group to write the assignment in,
356/// ASGN is the probe assignment to insert into the group,
357/// CELL is the generated probe wire to add to cells
358fn create_assignments<T: Clone>(
359    comp: &mut ir::Component,
360    sigs: &ir::LibrarySignatures,
361    group_names: &[Id],
362    structural_enable_map_opt: Option<CallsFromGroupMap<T>>,
363    cell_invoke_map_opt: Option<CallsFromGroupMap<T>>,
364    primitive_invoke_map_opt: Option<CallsFromGroupMap<T>>,
365) -> Vec<(
366    Id,
367    calyx_ir::Assignment<T>,
368    std::rc::Rc<std::cell::RefCell<calyx_ir::Cell>>,
369)> {
370    let delimiter = "___";
371    let comp_name = comp.name;
372    // build probe and assignments for every group (dynamic and static) + all structural invokes
373    let mut builder = ir::Builder::new(comp, sigs);
374    let one = builder.add_constant(1, 1);
375
376    // (group name, assignment to insert, probe cell to insert) for each probe we want to insert
377    // we assume that each probe cell will only have one assignment.
378    let mut group_name_assign_and_cell = Vec::new();
379
380    // probe and assignments for group enable (this group is currently active)
381    for group_name in group_names.iter() {
382        // store group and component name (differentiate between groups of the same name under different components)
383        let name = format!("{group_name}{delimiter}{comp_name}_group_probe");
384        let probe_cell = builder.add_primitive(name, "std_wire", &[1]);
385        let probe_asgn: ir::Assignment<T> = builder.build_assignment(
386            probe_cell.borrow().get("in"),
387            one.borrow().get("out"),
388            Guard::True,
389        );
390        // the probes should be @control because they should have value 0 whenever the corresponding group is not active.
391        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
392        probe_cell
393            .borrow_mut()
394            .add_attribute(BoolAttr::Protected, 1);
395        group_name_assign_and_cell.push((*group_name, probe_asgn, probe_cell));
396    }
397
398    if let Some(sem) = structural_enable_map_opt {
399        // probe and assignments for structural enables (this group is structurally enabling a child group)
400        for (invoked_group_name, parent_groups) in sem.iter() {
401            for (parent_group, guard) in parent_groups.iter() {
402                let probe_cell_name = format!(
403                    "{invoked_group_name}{delimiter}{parent_group}{delimiter}{comp_name}_se_probe"
404                );
405                let probe_cell =
406                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
407                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
408                probe_cell
409                    .borrow_mut()
410                    .add_attribute(BoolAttr::Protected, 1);
411                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
412                    probe_cell.borrow().get("in"),
413                    one.borrow().get("out"),
414                    guard.clone(),
415                );
416                group_name_assign_and_cell.push((
417                    *parent_group,
418                    probe_asgn,
419                    probe_cell,
420                ));
421            }
422        }
423    }
424
425    if let Some(cell_invoke_map) = cell_invoke_map_opt {
426        // probe cell and assignments for structural cell invocations (the group is structurally invoking a cell.)
427        for (invoker_group, invoked_cells) in cell_invoke_map.iter() {
428            for (invoked_cell, guard) in invoked_cells {
429                let probe_cell_name = format!(
430                    "{invoked_cell}{delimiter}{invoker_group}{delimiter}{comp_name}_cell_probe"
431                );
432                let probe_cell =
433                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
434                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
435                probe_cell
436                    .borrow_mut()
437                    .add_attribute(BoolAttr::Protected, 1);
438                // NOTE: this probe is active for the duration of the whole group. Hence, it may be active even when the cell itself is inactive.
439                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
440                    probe_cell.borrow().get("in"),
441                    one.borrow().get("out"),
442                    guard.clone(),
443                );
444                group_name_assign_and_cell.push((
445                    *invoker_group,
446                    probe_asgn,
447                    probe_cell,
448                ));
449            }
450        }
451    }
452
453    if let Some(primitive_invoke_map) = primitive_invoke_map_opt {
454        // probe and assignments for primitive invocations (this group is activating a primitive)
455        for (group, primitive_invs) in primitive_invoke_map.iter() {
456            for (primitive_cell_name, guard) in primitive_invs.iter() {
457                let probe_cell_name = format!(
458                    "{primitive_cell_name}{delimiter}{group}{delimiter}{comp_name}_primitive_probe"
459                );
460                let probe_cell =
461                    builder.add_primitive(probe_cell_name, "std_wire", &[1]);
462                probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
463                probe_cell
464                    .borrow_mut()
465                    .add_attribute(BoolAttr::Protected, 1);
466                let probe_asgn: ir::Assignment<T> = builder.build_assignment(
467                    probe_cell.borrow().get("in"),
468                    one.borrow().get("out"),
469                    guard.clone(),
470                );
471                group_name_assign_and_cell
472                    .push((*group, probe_asgn, probe_cell));
473            }
474        }
475    }
476
477    group_name_assign_and_cell
478}
479
480/// Creates probes for continuous assignments outside of groups. For every cell
481/// or primitive involved in a continuous assignment, this function will generate
482/// "contprimitive" and "contcell" wires as probes.
483fn continuous_assignments(
484    comp: &mut ir::Component,
485    sigs: &ir::LibrarySignatures,
486) {
487    // vector of cells (non-primitives) invoked
488    let mut cell_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
489    // vector of primitives invoked
490    let mut primitive_invoke_vec: Vec<(Id, ir::Guard<Nothing>)> = Vec::new();
491
492    // set to prevent adding multiple probes for a combinational primitive
493    let mut comb_primitives_covered = HashSet::new();
494    let mut comb_cells_covered = HashSet::new();
495    for assignment_ref in comp.continuous_assignments.iter() {
496        let dst_borrow = assignment_ref.dst.borrow();
497        let guard = *(assignment_ref.guard).clone();
498        if let ir::PortParent::Cell(cell_ref) = &dst_borrow.parent {
499            match cell_ref.upgrade().borrow().prototype.clone() {
500                calyx_ir::CellType::Primitive { .. } => {
501                    let cell_name = cell_ref.upgrade().borrow().name();
502                    // collecting primitives for area utilization; we want to avoid adding the same primitive twice!
503                    if comb_primitives_covered.insert(cell_name) {
504                        primitive_invoke_vec.push((cell_name, guard));
505                    }
506                }
507                calyx_ir::CellType::Component { .. } => {
508                    let cell_name = cell_ref.upgrade().borrow().name();
509                    if comb_cells_covered.insert(cell_name) {
510                        cell_invoke_vec.push((cell_name, guard));
511                    }
512                }
513                _ => (),
514            }
515        }
516    }
517
518    // add probes for primitives in continuous assignment
519    let delimiter = "___";
520    let comp_name = comp.name;
521    let mut builder = ir::Builder::new(comp, sigs);
522    let one = builder.add_constant(1, 1);
523    let mut assign_and_cell = Vec::new();
524    for (primitive_cell_name, guard) in primitive_invoke_vec.iter() {
525        let probe_cell_name = format!(
526            "{primitive_cell_name}{delimiter}{comp_name}_contprimitive_probe"
527        );
528        let probe_cell =
529            builder.add_primitive(probe_cell_name, "std_wire", &[1]);
530        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
531        probe_cell
532            .borrow_mut()
533            .add_attribute(BoolAttr::Protected, 1);
534        let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
535            probe_cell.borrow().get("in"),
536            one.borrow().get("out"),
537            guard.clone(),
538        );
539        assign_and_cell.push((probe_asgn, probe_cell));
540    }
541    // add probes for cells (non-primitives) in continuous assignment
542    for (cell_name, guard) in cell_invoke_vec.iter() {
543        let probe_cell_name =
544            format!("{cell_name}{delimiter}{comp_name}_contcell_probe");
545        let probe_cell =
546            builder.add_primitive(probe_cell_name, "std_wire", &[1]);
547        probe_cell.borrow_mut().add_attribute(BoolAttr::Control, 1);
548        probe_cell
549            .borrow_mut()
550            .add_attribute(BoolAttr::Protected, 1);
551        let probe_asgn: ir::Assignment<Nothing> = builder.build_assignment(
552            probe_cell.borrow().get("in"),
553            one.borrow().get("out"),
554            guard.clone(),
555        );
556        assign_and_cell.push((probe_asgn, probe_cell));
557    }
558
559    // Add created assignments to continuous assignments
560    for (asgn, cell) in assign_and_cell.iter() {
561        comp.continuous_assignments.push(asgn.clone());
562        comp.cells.add(cell.to_owned());
563    }
564}
565
566impl Visitor for ProfilerInstrumentation {
567    fn start(
568        &mut self,
569        comp: &mut ir::Component,
570        sigs: &ir::LibrarySignatures,
571        _comps: &[ir::Component],
572    ) -> VisResult {
573        group(comp, sigs);
574        combinational_group(comp, sigs);
575        static_group(comp, sigs);
576        continuous_assignments(comp, sigs);
577        Ok(Action::Continue)
578    }
579}