calyx_opt/passes/
compile_static.rs

1use super::math_utilities::get_bit_width_from;
2use crate::analysis::GraphColoring;
3use crate::traversal::{Action, Named, VisResult, Visitor};
4use calyx_ir as ir;
5use calyx_ir::{guard, structure, GetAttributes};
6use calyx_utils::Error;
7use ir::{build_assignments, Nothing, StaticTiming, RRC};
8use itertools::Itertools;
9use std::collections::{HashMap, HashSet};
10use std::ops::Not;
11use std::rc::Rc;
12
13#[derive(Default)]
14/// Compiles Static Islands
15pub struct CompileStatic {
16    /// maps original static group names to the corresponding group that has an FSM that reset early
17    reset_early_map: HashMap<ir::Id, ir::Id>,
18    /// maps group that has an FSM that resets early to its dynamic "wrapper" group name.
19    wrapper_map: HashMap<ir::Id, ir::Id>,
20    /// maps fsm names to their corresponding signal_reg
21    signal_reg_map: HashMap<ir::Id, ir::Id>,
22    /// maps reset_early_group names to (fsm name, fsm_width)
23    fsm_info_map: HashMap<ir::Id, (ir::Id, u64)>,
24    /// rewrites `static_group[go]` to `dynamic_group[go]`
25    group_rewrite: ir::rewriter::PortRewriteMap,
26}
27
28impl Named for CompileStatic {
29    fn name() -> &'static str {
30        "compile-static"
31    }
32
33    fn description() -> &'static str {
34        "compiles static sub-programs into a dynamic group"
35    }
36}
37
38// Takes in a static guard `guard`, and returns equivalent dynamic guard
39// The only thing that actually changes is the Guard::Info case
40// We need to turn static_timing to dynamic guards using `fsm`.
41// E.g.: %[2:3] gets turned into fsm.out >= 2 & fsm.out < 3
42pub(super) fn make_guard_dyn(
43    guard: ir::Guard<StaticTiming>,
44    fsm: &ir::RRC<ir::Cell>,
45    fsm_size: u64,
46    builder: &mut ir::Builder,
47    is_static_comp: bool,
48    comp_sig: Option<RRC<ir::Cell>>,
49) -> Box<ir::Guard<Nothing>> {
50    match guard {
51        ir::Guard::Or(l, r) => Box::new(ir::Guard::Or(
52            make_guard_dyn(
53                *l,
54                fsm,
55                fsm_size,
56                builder,
57                is_static_comp,
58                comp_sig.clone(),
59            ),
60            make_guard_dyn(
61                *r,
62                fsm,
63                fsm_size,
64                builder,
65                is_static_comp,
66                comp_sig,
67            ),
68        )),
69        ir::Guard::And(l, r) => Box::new(ir::Guard::And(
70            make_guard_dyn(
71                *l,
72                fsm,
73                fsm_size,
74                builder,
75                is_static_comp,
76                comp_sig.clone(),
77            ),
78            make_guard_dyn(
79                *r,
80                fsm,
81                fsm_size,
82                builder,
83                is_static_comp,
84                comp_sig,
85            ),
86        )),
87        ir::Guard::Not(g) => Box::new(ir::Guard::Not(make_guard_dyn(
88            *g,
89            fsm,
90            fsm_size,
91            builder,
92            is_static_comp,
93            comp_sig,
94        ))),
95        ir::Guard::CompOp(op, l, r) => Box::new(ir::Guard::CompOp(op, l, r)),
96        ir::Guard::Port(p) => Box::new(ir::Guard::Port(p)),
97        ir::Guard::True => Box::new(ir::Guard::True),
98        ir::Guard::Info(static_timing) => {
99            let (beg, end) = static_timing.get_interval();
100            if is_static_comp && beg == 0 && end == 1 {
101                let interval_const = builder.add_constant(0, fsm_size);
102                let sig = comp_sig.unwrap();
103                let g1 = guard!(sig["go"]);
104                let g2 = guard!(fsm["out"] == interval_const["out"]);
105                let g = ir::Guard::And(Box::new(g1), Box::new(g2));
106                return Box::new(g);
107            }
108            if beg + 1 == end {
109                // if beg + 1 == end then we only need to check if fsm == beg
110                let interval_const = builder.add_constant(beg, fsm_size);
111                let g = guard!(fsm["out"] == interval_const["out"]);
112                Box::new(g)
113            } else if beg == 0 {
114                // if beg == 0, then we only need to check if fsm < end
115                let end_const = builder.add_constant(end, fsm_size);
116                let lt: ir::Guard<Nothing> =
117                    guard!(fsm["out"] < end_const["out"]);
118                Box::new(lt)
119            } else {
120                // otherwise, check if fsm >= beg & fsm < end
121                let beg_const = builder.add_constant(beg, fsm_size);
122                let end_const = builder.add_constant(end, fsm_size);
123                let beg_guard: ir::Guard<Nothing> =
124                    guard!(fsm["out"] >= beg_const["out"]);
125                let end_guard: ir::Guard<Nothing> =
126                    guard!(fsm["out"] < end_const["out"]);
127                Box::new(ir::Guard::And(
128                    Box::new(beg_guard),
129                    Box::new(end_guard),
130                ))
131            }
132        }
133    }
134}
135
136// Takes in static assignment `assign` and returns a dynamic assignments
137// Mainly transforms the guards such that fsm.out >= 2 & fsm.out <= 3
138pub(super) fn make_assign_dyn(
139    assign: ir::Assignment<StaticTiming>,
140    fsm: &ir::RRC<ir::Cell>,
141    fsm_size: u64,
142    builder: &mut ir::Builder,
143    is_static_comp: bool,
144    comp_sig: Option<RRC<ir::Cell>>,
145) -> ir::Assignment<Nothing> {
146    ir::Assignment {
147        src: assign.src,
148        dst: assign.dst,
149        attributes: assign.attributes,
150        guard: make_guard_dyn(
151            *assign.guard,
152            fsm,
153            fsm_size,
154            builder,
155            is_static_comp,
156            comp_sig,
157        ),
158    }
159}
160
161// Given a list of `static_groups`, find the group named `name`.
162// If there is no such group, then there is an unreachable! error.
163fn find_static_group(
164    name: &ir::Id,
165    static_groups: &[ir::RRC<ir::StaticGroup>],
166) -> ir::RRC<ir::StaticGroup> {
167    Rc::clone(
168        static_groups
169            .iter()
170            .find(|static_group| static_group.borrow().name() == name)
171            .unwrap_or_else(|| {
172                unreachable!("couldn't find static group {name}")
173            }),
174    )
175}
176
177// Given an input static_group `sgroup`, finds the names of all of the groups
178// that it triggers through their go hole.
179// E.g., if `sgroup` has assignments that write to `sgroup1[go]` and `sgroup2[go]`
180// then return `{sgroup1, sgroup2}`
181// NOTE: assumes that static groups will only write the go holes of other static
182// groups, and never dynamic groups
183fn get_go_writes(sgroup: &ir::RRC<ir::StaticGroup>) -> HashSet<ir::Id> {
184    let mut uses = HashSet::new();
185    for asgn in &sgroup.borrow().assignments {
186        let dst = asgn.dst.borrow();
187        if dst.is_hole() && dst.name == "go" {
188            uses.insert(dst.get_parent_name());
189        }
190    }
191    uses
192}
193
194impl CompileStatic {
195    // returns an "early reset" group based on the information given
196    // in the arguments.
197    // sgroup_assigns are the static assignments of the group (they need to be
198    // changed to dynamic by instantiating an fsm, i.e., %[0,2] -> fsm.out < 2)
199    // name of early reset group has prefix "early_reset_{sgroup_name}"
200    fn make_early_reset_group(
201        &mut self,
202        sgroup_assigns: &mut Vec<ir::Assignment<ir::StaticTiming>>,
203        sgroup_name: ir::Id,
204        latency: u64,
205        attributes: ir::Attributes,
206        fsm: ir::RRC<ir::Cell>,
207        builder: &mut ir::Builder,
208    ) -> ir::RRC<ir::Group> {
209        let fsm_name = fsm.borrow().name();
210        let fsm_size = fsm
211            .borrow()
212            .find("out")
213            .unwrap_or_else(|| unreachable!("no `out` port on {fsm_name}"))
214            .borrow()
215            .width;
216        structure!( builder;
217            // done hole will be undefined bc of early reset
218            let ud = prim undef(1);
219            let signal_on = constant(1,1);
220            let adder = prim std_add(fsm_size);
221            let const_one = constant(1, fsm_size);
222            let first_state = constant(0, fsm_size);
223            let penultimate_state = constant(latency-1, fsm_size);
224        );
225        // create the dynamic group we will use to replace the static group
226        let mut early_reset_name = sgroup_name.to_string();
227        early_reset_name.insert_str(0, "early_reset_");
228        let g = builder.add_group(early_reset_name);
229        // converting static assignments to dynamic assignments
230        let mut assigns = sgroup_assigns
231            .drain(..)
232            .map(|assign| {
233                make_assign_dyn(assign, &fsm, fsm_size, builder, false, None)
234            })
235            .collect_vec();
236        // assignments to increment the fsm
237        let not_penultimate_state_guard: ir::Guard<ir::Nothing> =
238            guard!(fsm["out"] != penultimate_state["out"]);
239        let penultimate_state_guard: ir::Guard<ir::Nothing> =
240            guard!(fsm["out"] == penultimate_state["out"]);
241        let fsm_incr_assigns = build_assignments!(
242          builder;
243          // increments the fsm
244          adder["left"] = ? fsm["out"];
245          adder["right"] = ? const_one["out"];
246          fsm["write_en"] = ? signal_on["out"];
247          fsm["in"] = not_penultimate_state_guard ? adder["out"];
248           // resets the fsm early
249          fsm["in"] = penultimate_state_guard ? first_state["out"];
250          // will never reach this guard since we are resetting when we get to
251          // the penultimate state
252          g["done"] = ? ud["out"];
253        );
254        assigns.extend(fsm_incr_assigns.to_vec());
255        // maps the "early reset" group name to the "fsm name" that it borrows.
256        // this is helpful when we build the "wrapper group"
257        self.fsm_info_map
258            .insert(g.borrow().name(), (fsm.borrow().name(), fsm_size));
259        // adding the assignments to the new dynamic group and creating a
260        // new (dynamic) enable
261        g.borrow_mut().assignments = assigns;
262        g.borrow_mut().attributes = attributes;
263        g
264    }
265
266    fn build_wrapper_group(
267        fsm_name: &ir::Id,
268        fsm_width: u64,
269        group_name: &ir::Id,
270        signal_reg: ir::RRC<ir::Cell>,
271        builder: &mut ir::Builder,
272        add_continuous_assigns: bool,
273    ) -> ir::RRC<ir::Group> {
274        // get the groups/fsm necessary to build the wrapper group
275        let early_reset_group = builder
276            .component
277            .get_groups()
278            .find(*group_name)
279            .unwrap_or_else(|| {
280                unreachable!(
281                    "called build_wrapper_group with {}, which is not a group",
282                    group_name
283                )
284            });
285        let early_reset_fsm =
286            builder.component.find_cell(*fsm_name).unwrap_or_else(|| {
287                unreachable!(
288                    "called build_wrapper_group with {}, which is not an fsm",
289                    fsm_name
290                )
291            });
292
293        structure!( builder;
294            let state_zero = constant(0, fsm_width);
295            let signal_on = constant(1, 1);
296            let signal_off = constant(0, 1);
297        );
298        // make guards
299        // fsm.out == 0 ?
300        let first_state: ir::Guard<ir::Nothing> =
301            guard!(early_reset_fsm["out"] == state_zero["out"]);
302        // signal_reg.out ?
303        let signal_reg_guard: ir::Guard<ir::Nothing> =
304            guard!(signal_reg["out"]);
305        // !signal_reg.out ?
306        let not_signal_reg = signal_reg_guard.clone().not();
307        // fsm.out == 0 & signal_reg.out ?
308        let first_state_and_signal = first_state.clone() & signal_reg_guard;
309        // fsm.out == 0 & ! signal_reg.out ?
310        let first_state_and_not_signal = first_state & not_signal_reg;
311        // create the wrapper group for early_reset_group
312        let mut wrapper_name = group_name.clone().to_string();
313        wrapper_name.insert_str(0, "wrapper_");
314        let g = builder.add_group(wrapper_name);
315        let group_assigns = build_assignments!(
316          builder;
317          // early_reset_group[go] = 1'd1
318          early_reset_group["go"] = ? signal_on["out"];
319          // when fsm == 0, and !signal_reg, then set signal_reg to high
320          signal_reg["write_en"] = first_state_and_not_signal ? signal_on["out"];
321          signal_reg["in"] =  first_state_and_not_signal ? signal_on["out"];
322          // group[done] = fsm.out == 0 & signal_reg.out ? 1'd1
323          g["done"] = first_state_and_signal ? signal_on["out"];
324        );
325        if add_continuous_assigns {
326            // continuous assignments to reset signal_reg back to 0 when the wrapper is done
327            let continuous_assigns = build_assignments!(
328                builder;
329                // when (fsm == 0 & signal_reg is high), which is the done condition of the wrapper,
330                // reset the signal_reg back to low
331                signal_reg["write_en"] = first_state_and_signal ? signal_on["out"];
332                signal_reg["in"] =  first_state_and_signal ? signal_off["out"];
333            );
334            builder.add_continuous_assignments(continuous_assigns.to_vec());
335        }
336        g.borrow_mut().assignments = group_assigns.to_vec();
337        g.borrow_mut().attributes =
338            early_reset_group.borrow().attributes.clone();
339        g
340    }
341
342    fn get_reset_group_name(&self, sc: &mut ir::StaticControl) -> &ir::Id {
343        // assume that there are only static enables left.
344        // if there are any other type of static control, then error out.
345        let ir::StaticControl::Enable(s) = sc else {
346            unreachable!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name());
347        };
348
349        let sgroup = s.group.borrow_mut();
350        let sgroup_name = sgroup.name();
351        // get the "early reset group". It should exist, since we made an
352        // early_reset group for every static group in the component
353        let early_reset_name =
354            self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
355                unreachable!(
356                    "group {} not in self.reset_early_map",
357                    sgroup_name
358                )
359            });
360
361        early_reset_name
362    }
363
364    /// compile `while` whose body is `static` control such that at the end of each
365    /// iteration, the checking of condition does not incur an extra cycle of
366    /// latency.
367    /// We do this by wrapping the early reset group of the body with
368    /// another wrapper group, which sets the go signal of the early reset group
369    /// high, and is done when at the 0th cycle of each iteration, the condtion
370    /// port is done.
371    /// Note: this only works if the port for the while condition is `@stable`.
372    fn build_wrapper_group_while(
373        &self,
374        fsm_name: &ir::Id,
375        fsm_width: u64,
376        group_name: &ir::Id,
377        port: RRC<ir::Port>,
378        builder: &mut ir::Builder,
379    ) -> RRC<ir::Group> {
380        let reset_early_group = builder
381            .component
382            .find_group(*group_name)
383            .unwrap_or_else(|| {
384                unreachable!(
385                    "called build_wrapper_group with {}, which is not a group",
386                    group_name
387                )
388            });
389        let early_reset_fsm =
390            builder.component.find_cell(*fsm_name).unwrap_or_else(|| {
391                unreachable!(
392                    "called build_wrapper_group with {}, which is not an fsm",
393                    fsm_name
394                )
395            });
396
397        let wrapper_group =
398            builder.add_group(format!("while_wrapper_{}", group_name));
399
400        structure!(
401            builder;
402            let one = constant(1, 1);
403            let time_0 = constant(0, fsm_width);
404        );
405
406        let port_parent = port.borrow().cell_parent();
407        let port_name = port.borrow().name;
408        let done_guard = guard!(port_parent[port_name]).not()
409            & guard!(early_reset_fsm["out"] == time_0["out"]);
410
411        let assignments = build_assignments!(
412            builder;
413            // reset_early_group[go] = 1'd1;
414            // wrapper_group[done] = !port ? 1'd1;
415            reset_early_group["go"] = ? one["out"];
416            wrapper_group["done"] = done_guard ? one["out"];
417        );
418
419        wrapper_group.borrow_mut().assignments.extend(assignments);
420        wrapper_group
421    }
422
423    // Gets all of the triggered static groups within `c`, and adds it to `cur_names`.
424    // Relies on sgroup_uses_map to take into account groups that are triggered through
425    // their `go` hole.
426    fn get_used_sgroups(
427        c: &ir::Control,
428        cur_names: &mut HashSet<ir::Id>,
429        sgroup_uses_map: &HashMap<ir::Id, HashSet<ir::Id>>,
430    ) {
431        match c {
432            ir::Control::Empty(_)
433            | ir::Control::Enable(_)
434            | ir::Control::Invoke(_) => (),
435            ir::Control::Static(sc) => {
436                let ir::StaticControl::Enable(s) = sc else {
437                    unreachable!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name());
438                };
439                let group_name = s.group.borrow().name();
440                if let Some(sgroup_uses) = sgroup_uses_map.get(&group_name) {
441                    cur_names.extend(sgroup_uses);
442                }
443                cur_names.insert(group_name);
444            }
445            ir::Control::Par(ir::Par { stmts, .. })
446            | ir::Control::Seq(ir::Seq { stmts, .. }) => {
447                for stmt in stmts {
448                    Self::get_used_sgroups(stmt, cur_names, sgroup_uses_map);
449                }
450            }
451            ir::Control::Repeat(ir::Repeat { body, .. })
452            | ir::Control::While(ir::While { body, .. }) => {
453                Self::get_used_sgroups(body, cur_names, sgroup_uses_map);
454            }
455            ir::Control::If(if_stmt) => {
456                Self::get_used_sgroups(
457                    &if_stmt.tbranch,
458                    cur_names,
459                    sgroup_uses_map,
460                );
461                Self::get_used_sgroups(
462                    &if_stmt.fbranch,
463                    cur_names,
464                    sgroup_uses_map,
465                );
466            }
467        }
468    }
469
470    /// Given control `c`, adds conflicts to `conflict_graph` between all
471    /// static groups that are executed in separate threads of the same par block.
472    /// `sgroup_uses_map` maps:
473    /// static group names -> all of the static groups that it triggers the go ports
474    /// of (even recursively).
475    /// Example: group A {B[go] = 1;} group B {C[go] = 1} group C{}
476    /// Would map: A -> {B,C} and B -> {C}
477    fn add_par_conflicts(
478        c: &ir::Control,
479        sgroup_uses_map: &HashMap<ir::Id, HashSet<ir::Id>>,
480        conflict_graph: &mut GraphColoring<ir::Id>,
481    ) {
482        match c {
483            ir::Control::Empty(_)
484            | ir::Control::Enable(_)
485            | ir::Control::Invoke(_)
486            | ir::Control::Static(_) => (),
487            ir::Control::Seq(seq) => {
488                for stmt in &seq.stmts {
489                    Self::add_par_conflicts(
490                        stmt,
491                        sgroup_uses_map,
492                        conflict_graph,
493                    );
494                }
495            }
496            ir::Control::Repeat(ir::Repeat { body, .. })
497            | ir::Control::While(ir::While { body, .. }) => {
498                Self::add_par_conflicts(body, sgroup_uses_map, conflict_graph)
499            }
500            ir::Control::If(if_stmt) => {
501                Self::add_par_conflicts(
502                    &if_stmt.tbranch,
503                    sgroup_uses_map,
504                    conflict_graph,
505                );
506                Self::add_par_conflicts(
507                    &if_stmt.fbranch,
508                    sgroup_uses_map,
509                    conflict_graph,
510                );
511            }
512            ir::Control::Par(par) => {
513                // sgroup_conflict_vec is a vec of HashSets.
514                // Each entry of the vec corresponds to a par thread, and holds
515                // all of the groups executed in that thread.
516                let mut sgroup_conflict_vec = Vec::new();
517                for stmt in &par.stmts {
518                    let mut used_sgroups = HashSet::new();
519                    Self::get_used_sgroups(
520                        stmt,
521                        &mut used_sgroups,
522                        sgroup_uses_map,
523                    );
524                    sgroup_conflict_vec.push(used_sgroups);
525                }
526                for (thread1_sgroups, thread2_sgroups) in
527                    sgroup_conflict_vec.iter().tuple_combinations()
528                {
529                    for sgroup1 in thread1_sgroups {
530                        for sgroup2 in thread2_sgroups {
531                            conflict_graph.insert_conflict(sgroup1, sgroup2);
532                        }
533                    }
534                }
535                // Necessary to add conflicts between nested pars
536                for stmt in &par.stmts {
537                    Self::add_par_conflicts(
538                        stmt,
539                        sgroup_uses_map,
540                        conflict_graph,
541                    );
542                }
543            }
544        }
545    }
546
547    /// Given an `sgroup_uses_map`, which maps:
548    /// static group names -> all of the static groups that it triggers the go ports
549    /// of (even recursively).
550    /// Example: group A {B[go] = 1;} group B {C[go] = 1} group C{}
551    /// Would map: A -> {B,C} and B -> {C}
552    /// Adds conflicts between any groups triggered at the same time based on
553    /// `go` port triggering.
554    fn add_go_port_conflicts(
555        sgroup_uses_map: &HashMap<ir::Id, HashSet<ir::Id>>,
556        conflict_graph: &mut GraphColoring<ir::Id>,
557    ) {
558        for (sgroup, sgroup_uses) in sgroup_uses_map {
559            for sgroup_use in sgroup_uses {
560                conflict_graph.insert_conflict(sgroup_use, sgroup);
561            }
562            // If multiple groups are triggered by the same group, then
563            // we conservatively add a conflict between such groups
564            for (sgroup_use1, sgroup_use2) in
565                sgroup_uses.iter().tuple_combinations()
566            {
567                conflict_graph.insert_conflict(sgroup_use1, sgroup_use2);
568            }
569        }
570    }
571
572    // Given a "coloring" of static group names -> their "colors",
573    // instantiate one fsm per color and return a hashmap that maps
574    // fsm names -> groups that it handles
575    fn build_fsm_mapping(
576        coloring: HashMap<ir::Id, ir::Id>,
577        static_groups: &[ir::RRC<ir::StaticGroup>],
578        builder: &mut ir::Builder,
579    ) -> HashMap<ir::Id, HashSet<ir::Id>> {
580        // "reverse" the coloring to map colors -> static group_names
581        let mut color_to_groups: HashMap<ir::Id, HashSet<ir::Id>> =
582            HashMap::new();
583        for (group, color) in coloring {
584            color_to_groups.entry(color).or_default().insert(group);
585        }
586        // Need deterministic ordering for testing.
587        let mut vec_color_to_groups: Vec<(ir::Id, HashSet<ir::Id>)> =
588            color_to_groups.into_iter().collect();
589        vec_color_to_groups
590            .sort_by(|(color1, _), (color2, _)| color1.cmp(color2));
591        vec_color_to_groups.into_iter().map(|(color, group_names)| {
592            // For each color, build an FSM that has the number of bits required
593            // for the largest latency in `group_names`
594            let max_latency = group_names
595                .iter()
596                .map(|g| {
597                    find_static_group(g, static_groups).borrow()
598                        .latency
599                })
600                .max().unwrap_or_else(|| unreachable!("group {color} had no corresponding groups in its coloring map")
601                );
602            let fsm_size = get_bit_width_from(
603                max_latency + 1, /* represent 0..latency */
604            );
605            structure!( builder;
606                let fsm = prim std_reg(fsm_size);
607            );
608            let fsm_name = fsm.borrow().name();
609            (fsm_name, group_names)
610        }).collect()
611    }
612
613    // helper to `build_sgroup_uses_map`
614    // `parent_group` is the group that we are "currently" analyzing
615    // `full_group_ancestry` is the "ancestry of the group we are analyzing"
616    // Example: group A {B[go] = 1;} group B {C[go] = 1} group C{}, and `parent_group`
617    // is B, then ancestry would be B and A.
618    // `cur_mapping` is the current_mapping for `sgroup_uses_map`
619    // `group_names` is a vec of group_names. Once we analyze a group, we should
620    // remove it from group_names
621    // `sgroups` is a vec of static groups.
622    fn update_sgroup_uses_map(
623        parent_group: &ir::Id,
624        full_group_ancestry: &mut HashSet<ir::Id>,
625        cur_mapping: &mut HashMap<ir::Id, HashSet<ir::Id>>,
626        group_names: &mut HashSet<ir::Id>,
627        sgroups: &Vec<ir::RRC<ir::StaticGroup>>,
628    ) {
629        let group_uses =
630            get_go_writes(&find_static_group(parent_group, sgroups));
631        for group_use in group_uses {
632            for ancestor in full_group_ancestry.iter() {
633                cur_mapping.entry(*ancestor).or_default().insert(group_use);
634            }
635            full_group_ancestry.insert(group_use);
636            Self::update_sgroup_uses_map(
637                &group_use,
638                full_group_ancestry,
639                cur_mapping,
640                group_names,
641                sgroups,
642            );
643            full_group_ancestry.remove(&group_use);
644        }
645        group_names.remove(parent_group);
646    }
647
648    /// Builds an `sgroup_uses_map`, which maps:
649    /// static group names -> all of the static groups that it triggers the go ports
650    /// of (even recursively).
651    /// Example: group A {B[go] = 1;} group B {C[go] = 1} group C{}
652    /// Would map: A -> {B,C} and B -> {C}
653    fn build_sgroup_uses_map(
654        sgroups: &Vec<ir::RRC<ir::StaticGroup>>,
655    ) -> HashMap<ir::Id, HashSet<ir::Id>> {
656        let mut names: HashSet<ir::Id> = sgroups
657            .iter()
658            .map(|sgroup| sgroup.borrow().name())
659            .collect();
660        let mut cur_mapping = HashMap::new();
661        while !names.is_empty() {
662            let random_group = *names.iter().next().unwrap();
663            Self::update_sgroup_uses_map(
664                &random_group,
665                &mut HashSet::from([random_group]),
666                &mut cur_mapping,
667                &mut names,
668                sgroups,
669            )
670        }
671        cur_mapping
672    }
673}
674
675impl Visitor for CompileStatic {
676    fn start(
677        &mut self,
678        comp: &mut ir::Component,
679        sigs: &ir::LibrarySignatures,
680        _comps: &[ir::Component],
681    ) -> VisResult {
682        let sgroups: Vec<ir::RRC<ir::StaticGroup>> =
683            comp.get_static_groups_mut().drain().collect();
684        // `sgroup_uses_map` builds a mapping of static groups -> groups that
685        // it (even indirectly) triggers the `go` port of.
686        let sgroup_uses_map = Self::build_sgroup_uses_map(&sgroups);
687        // Build conflict graph and get coloring.
688        let mut conflict_graph: GraphColoring<ir::Id> =
689            GraphColoring::from(sgroups.iter().map(|g| g.borrow().name()));
690        Self::add_par_conflicts(
691            &comp.control.borrow(),
692            &sgroup_uses_map,
693            &mut conflict_graph,
694        );
695        Self::add_go_port_conflicts(&sgroup_uses_map, &mut conflict_graph);
696        let coloring = conflict_graph.color_greedy(None, true);
697        let mut builder = ir::Builder::new(comp, sigs);
698        // build Mappings of fsm names -> set of groups that it can handle.
699        let fsm_mappings =
700            Self::build_fsm_mapping(coloring, &sgroups, &mut builder);
701        let mut groups_to_fsms = HashMap::new();
702        // "Reverses" fsm_mappings to map group names -> fsm cells
703        for (fsm_name, group_names) in fsm_mappings {
704            let fsm = builder.component.find_guaranteed_cell(fsm_name);
705            for group_name in group_names {
706                groups_to_fsms.insert(group_name, Rc::clone(&fsm));
707            }
708        }
709
710        // create "early reset" dynamic groups that never reach set their done hole
711        for sgroup in sgroups.iter() {
712            let mut sgroup_ref = sgroup.borrow_mut();
713            let sgroup_name = sgroup_ref.name();
714            let sgroup_latency = sgroup_ref.get_latency();
715            let sgroup_attributes = sgroup_ref.attributes.clone();
716            let sgroup_assigns = &mut sgroup_ref.assignments;
717            let g = self.make_early_reset_group(
718                sgroup_assigns,
719                sgroup_name,
720                sgroup_latency,
721                sgroup_attributes,
722                Rc::clone(groups_to_fsms.get(&sgroup_name).unwrap_or_else(
723                    || unreachable!("{sgroup_name} has no corresponding fsm"),
724                )),
725                &mut builder,
726            );
727            // map the static group name -> early reset group name
728            // helpful for rewriting control
729            self.reset_early_map.insert(sgroup_name, g.borrow().name());
730            // group_rewrite_map helps write static_group[go] to early_reset_group[go]
731            // technically could do this w/ early_reset_map but is easier w/
732            // group_rewrite, which is explicitly of type `PortRewriterMap`
733            self.group_rewrite.insert(
734                ir::Canonical::new(sgroup_name, ir::Id::from("go")),
735                g.borrow().find("go").unwrap_or_else(|| {
736                    unreachable!("group {} has no go port", g.borrow().name())
737                }),
738            );
739        }
740
741        // rewrite static_group[go] to early_reset_group[go]
742        // don't have to worry about writing static_group[done] b/c static
743        // groups don't have done holes.
744        comp.for_each_assignment(|assign| {
745            assign.for_each_port(|port| {
746                self.group_rewrite
747                    .get(&port.borrow().canonical())
748                    .map(Rc::clone)
749            })
750        });
751
752        comp.get_static_groups_mut().append(sgroups.into_iter());
753
754        Ok(Action::Continue)
755    }
756
757    /// Executed after visiting the children of a [ir::Static] node.
758    fn start_static_control(
759        &mut self,
760        sc: &mut ir::StaticControl,
761        comp: &mut ir::Component,
762        sigs: &ir::LibrarySignatures,
763        _comps: &[ir::Component],
764    ) -> VisResult {
765        // assume that there are only static enables left.
766        // if there are any other type of static control, then error out.
767        let ir::StaticControl::Enable(s) = sc else {
768            return Err(Error::malformed_control(format!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name())));
769        };
770
771        let sgroup = s.group.borrow_mut();
772        let sgroup_name = sgroup.name();
773        // get the "early reset group". It should exist, since we made an
774        // early_reset group for every static group in the component
775        let early_reset_name =
776            self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
777                unreachable!(
778                    "group {} early reset has not been created",
779                    sgroup_name
780                )
781            });
782        // check if we've already built the wrapper group for early_reset_group
783        // if so, we can just use that, otherwise, we must build the wrapper group
784        let group_choice = match self.wrapper_map.get(early_reset_name) {
785            None => {
786                // create the builder/cells that we need to create wrapper group
787                let mut builder = ir::Builder::new(comp, sigs);
788                let (fsm_name, fsm_width )= self.fsm_info_map.get(early_reset_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", early_reset_name));
789                // If we've already made a wrapper for a group that uses the same
790                // FSM, we can reuse the signal_reg. Otherwise, we must
791                // instantiate a new signal_reg.
792                let wrapper = match self.signal_reg_map.get(fsm_name) {
793                    None => {
794                        // Need to build the signal_reg and the continuous
795                        // assignment that resets the signal_reg
796                        structure!( builder;
797                            let signal_reg = prim std_reg(1);
798                        );
799                        self.signal_reg_map
800                            .insert(*fsm_name, signal_reg.borrow().name());
801                        Self::build_wrapper_group(
802                            fsm_name,
803                            *fsm_width,
804                            early_reset_name,
805                            signal_reg,
806                            &mut builder,
807                            true,
808                        )
809                    }
810                    Some(reg_name) => {
811                        // Already_built the signal_reg.
812                        // We don't need to add continuous assignments
813                        // that resets signal_reg.
814                        let signal_reg = builder
815                            .component
816                            .find_cell(*reg_name)
817                            .unwrap_or_else(|| {
818                                unreachable!("signal reg {reg_name} found")
819                            });
820                        Self::build_wrapper_group(
821                            fsm_name,
822                            *fsm_width,
823                            early_reset_name,
824                            signal_reg,
825                            &mut builder,
826                            false,
827                        )
828                    }
829                };
830                self.wrapper_map
831                    .insert(*early_reset_name, wrapper.borrow().name());
832                wrapper
833            }
834            Some(name) => comp.find_group(*name).unwrap(),
835        };
836
837        let mut e = ir::Control::enable(group_choice);
838        let attrs = std::mem::take(&mut s.attributes);
839        *e.get_mut_attributes() = attrs;
840        Ok(Action::Change(Box::new(e)))
841    }
842
843    /// if while body is static, then we want to make sure that the while
844    /// body does not take the extra cycle incurred by the done condition
845    /// So we replace the while loop with `enable` of a wrapper group
846    /// that sets the go signal of the static group in the while loop body high
847    /// (all static control should be compiled into static groups by
848    /// `static_inliner` now). The done signal of the wrapper group should be
849    /// the condition that the fsm of the while body is %0 and the port signal
850    /// is 1'd0.
851    /// For example, we replace
852    /// ```
853    /// wires {
854    /// static group A<1> {
855    ///     ...
856    ///   }
857    ///    ...
858    /// }
859    /// control {
860    ///   while l.out {
861    ///     A;
862    ///   }
863    /// }
864    /// ```
865    /// with
866    /// ```
867    /// wires {
868    ///  group early_reset_A {
869    ///     ...
870    ///        }
871    ///
872    /// group while_wrapper_early_reset_A {
873    ///       early_reset_A[go] = 1'd1;
874    ///       while_wrapper_early_reset_A[done] = !l.out & fsm.out == 1'd0 ? 1'd1;
875    ///     }
876    ///   }
877    ///   control {
878    ///     while_wrapper_early_reset_A;
879    ///   }
880    /// ```
881    fn start_while(
882        &mut self,
883        s: &mut ir::While,
884        comp: &mut ir::Component,
885        sigs: &ir::LibrarySignatures,
886        _comps: &[ir::Component],
887    ) -> VisResult {
888        if s.cond.is_none() {
889            if let ir::Control::Static(sc) = &mut *(s.body) {
890                let mut builder = ir::Builder::new(comp, sigs);
891                let reset_group_name = self.get_reset_group_name(sc);
892
893                // Get fsm for reset_group
894                let (fsm, fsm_width) = self.fsm_info_map.get(reset_group_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", reset_group_name));
895                let wrapper_group = self.build_wrapper_group_while(
896                    fsm,
897                    *fsm_width,
898                    reset_group_name,
899                    Rc::clone(&s.port),
900                    &mut builder,
901                );
902                let c = ir::Control::enable(wrapper_group);
903                return Ok(Action::change(c));
904            }
905        }
906
907        Ok(Action::Continue)
908    }
909
910    fn finish(
911        &mut self,
912        comp: &mut ir::Component,
913        _sigs: &ir::LibrarySignatures,
914        _comps: &[ir::Component],
915    ) -> VisResult {
916        // make sure static groups have no assignments, since
917        // we should have already drained the assignments in static groups
918        for g in comp.get_static_groups() {
919            if !g.borrow().assignments.is_empty() {
920                unreachable!("Should have converted all static groups to dynamic. {} still has assignments in it", g.borrow().name());
921            }
922        }
923        // remove all static groups
924        comp.get_static_groups_mut().retain(|_| false);
925        Ok(Action::Continue)
926    }
927}