calyx_opt/passes/
compile_static.rs

1use crate::analysis::{
2    GraphColoring, Node, ParNodes, SingleNode, StateType, StaticFSM,
3};
4use crate::traversal::{
5    Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor,
6};
7use calyx_ir::{self as ir, Nothing, PortParent};
8use calyx_ir::{GetAttributes, guard, structure};
9use calyx_utils::{CalyxResult, Error};
10use core::panic;
11use ir::{RRC, build_assignments};
12use itertools::Itertools;
13use std::collections::{BTreeMap, HashMap, HashSet};
14use std::ops::Not;
15use std::rc::Rc;
16use std::vec;
17
18type OptionalStaticFSM = Option<ir::RRC<StaticFSM>>;
19
20/// Compiles Static Islands
21pub struct CompileStatic {
22    /// maps original static group names to the corresponding group that has an FSM that reset early
23    reset_early_map: HashMap<ir::Id, ir::Id>,
24    /// maps group that has an FSM that resets early to its dynamic "wrapper" group name.
25    wrapper_map: HashMap<ir::Id, ir::Id>,
26    /// maps fsm names to their corresponding signal_reg
27    signal_reg_map: HashMap<ir::Id, ir::Id>,
28    /// maps reset_early_group names to (fsm_identifier, fsm_first_state, final_fsm_state);
29    /// The "fsm identifier" is just the name of the fsm (if it exists) and
30    /// some other unique identifier if it doesn't exist (this works because
31    /// it is always fine to give each entry group its own completely unique identifier.)
32    fsm_info_map:
33        HashMap<ir::Id, (ir::Id, ir::Guard<Nothing>, ir::Guard<Nothing>)>,
34    /// Maps `static_group[go]` to `early_reset_group[go]`.
35    group_rewrites: ir::rewriter::PortRewriteMap,
36
37    /// Command line arguments:
38    /// Cutoff for one hot encoding. Anything larger than the cutoff becomes
39    /// binary.
40    one_hot_cutoff: u64,
41    /// Bool indicating whether to make the FSM pause (i.e., stop counting) when
42    /// offloading. In order for compilation to make sense, this parameter must
43    /// match the parameter for `static-inline`.
44    offload_pause: bool,
45    /// Bool indicating whether to greedily share the FSM registers
46    greedy_share: bool,
47}
48
49impl Named for CompileStatic {
50    fn name() -> &'static str {
51        "compile-static"
52    }
53
54    fn description() -> &'static str {
55        "compiles static sub-programs into a dynamic group"
56    }
57
58    fn opts() -> Vec<PassOpt> {
59        vec![PassOpt::new(
60            "one-hot-cutoff",
61            "The upper limit on the number of states the static FSM must have before we pick binary
62            encoding over one-hot. Defaults to 0 (i.e., always choose binary encoding)",
63            ParseVal::Num(0),
64            PassOpt::parse_num,
65        ),
66        PassOpt::new(
67            "offload-pause",
68            "Whether to pause the static FSM when offloading. Note that this
69            parameter must be in sync with the static-inliner's offload-pause
70            parameter for compilation to work correctly",
71            ParseVal::Bool(true),
72            PassOpt::parse_bool,
73        ),
74        PassOpt::new(
75            "greedy-share",
76            "Whether to greedily share the FSMs",
77            ParseVal::Bool(true),
78            PassOpt::parse_bool,
79        )
80
81        ]
82    }
83}
84
85impl ConstructVisitor for CompileStatic {
86    fn from(ctx: &ir::Context) -> CalyxResult<Self> {
87        let opts = Self::get_opts(ctx);
88
89        Ok(CompileStatic {
90            one_hot_cutoff: opts["one-hot-cutoff"].pos_num().unwrap(),
91            offload_pause: opts["offload-pause"].bool(),
92            greedy_share: opts["greedy-share"].bool(),
93            reset_early_map: HashMap::new(),
94            wrapper_map: HashMap::new(),
95            signal_reg_map: HashMap::new(),
96            fsm_info_map: HashMap::new(),
97            group_rewrites: ir::rewriter::PortRewriteMap::default(),
98        })
99    }
100
101    fn clear_data(&mut self) {
102        self.reset_early_map = HashMap::new();
103        self.wrapper_map = HashMap::new();
104        self.signal_reg_map = HashMap::new();
105        self.fsm_info_map = HashMap::new();
106        self.group_rewrites = ir::rewriter::PortRewriteMap::default();
107    }
108}
109
110impl CompileStatic {
111    /// Builds a wrapper group for group named group_name using fsm_final_state
112    /// and a signal_reg.
113    /// We set the signal_reg high on the final fsm state, since we know the
114    /// `done` signal should be high the next cycle after that.
115    /// `add_resetting_logic` is a bool; since the same FSM/signal_reg pairing
116    /// may be used for multiple static islands, and we only add resetting logic
117    /// for the signal_reg once.
118    fn build_wrapper_group(
119        fsm_final_state: ir::Guard<Nothing>,
120        group_name: &ir::Id,
121        signal_reg: ir::RRC<ir::Cell>,
122        builder: &mut ir::Builder,
123        add_reseting_logic: bool,
124    ) -> ir::RRC<ir::Group> {
125        // Get the group and fsm necessary to build the wrapper group.
126        let early_reset_group = builder
127            .component
128            .get_groups()
129            .find(*group_name)
130            .unwrap_or_else(|| {
131                unreachable!(
132                    "called build_wrapper_group with {}, which is not a group",
133                    group_name
134                )
135            });
136
137        structure!( builder;
138            let signal_on = constant(1, 1);
139            let signal_off = constant(0, 1);
140        );
141        // Making the rest of the guards guards:
142        // signal_reg.out
143        let signal_reg_guard: ir::Guard<ir::Nothing> =
144            guard!(signal_reg["out"]);
145        // !signal_reg.out
146        let not_signal_reg = signal_reg_guard.clone().not();
147        // <fsm in final state> & ! signal_reg.out
148        let final_state_not_signal = fsm_final_state & not_signal_reg;
149
150        // create the wrapper group for early_reset_group
151        let mut wrapper_name = group_name.clone().to_string();
152        wrapper_name.insert_str(0, "wrapper_");
153        let g = builder.add_group(wrapper_name);
154        let group_assigns = build_assignments!(
155          builder;
156          // early_reset_group[go] = 1'd1
157          early_reset_group["go"] = ? signal_on["out"];
158          // when <fsm_in_final_state> and !signal_reg, then set signal_reg to high
159          signal_reg["write_en"] = final_state_not_signal ? signal_on["out"];
160          signal_reg["in"] =  final_state_not_signal ? signal_on["out"];
161          // group[done] = signal_reg.out ? 1'd1
162          g["done"] = signal_reg_guard ? signal_on["out"];
163        );
164        if add_reseting_logic {
165            // continuous assignments to reset signal_reg back to 0 when the wrapper is done
166            let continuous_assigns = build_assignments!(
167                builder;
168                // when (fsm == 0 & signal_reg is high), which is the done condition of the wrapper,
169                // reset the signal_reg back to low
170                signal_reg["write_en"] = signal_reg_guard ? signal_on["out"];
171                signal_reg["in"] =  signal_reg_guard ? signal_off["out"];
172            );
173            builder.add_continuous_assignments(continuous_assigns.to_vec());
174        }
175        g.borrow_mut().assignments = group_assigns.to_vec();
176        g.borrow_mut().attributes =
177            early_reset_group.borrow().attributes.clone();
178        g
179    }
180
181    /// compile `while` whose body is `static` control such that at the end of each
182    /// iteration, the checking of condition does not incur an extra cycle of
183    /// latency.
184    /// We do this by wrapping the early reset group of the body with
185    /// another wrapper group, which sets the go signal of the early reset group
186    /// high, and is done when at the 0th cycle of each iteration, the condtion
187    /// port is done.
188    /// Note: this only works if the port for the while condition is `@stable`.
189    fn build_wrapper_group_while(
190        &self,
191        fsm_first_state: ir::Guard<Nothing>,
192        group_name: &ir::Id,
193        port: RRC<ir::Port>,
194        builder: &mut ir::Builder,
195    ) -> RRC<ir::Group> {
196        let reset_early_group = builder
197            .component
198            .find_group(*group_name)
199            .unwrap_or_else(|| {
200                unreachable!(
201                    "called build_wrapper_group with {}, which is not a group",
202                    group_name
203                )
204            });
205
206        let wrapper_group =
207            builder.add_group(format!("while_wrapper_{group_name}"));
208
209        structure!(
210            builder;
211            let one = constant(1, 1);
212        );
213
214        let port_parent = port.borrow().cell_parent();
215        let port_name = port.borrow().name;
216        let done_guard = guard!(port_parent[port_name]).not() & fsm_first_state;
217
218        let assignments = build_assignments!(
219            builder;
220            reset_early_group["go"] = ? one["out"];
221            wrapper_group["done"] = done_guard ? one["out"];
222        );
223
224        wrapper_group.borrow_mut().assignments.extend(assignments);
225        wrapper_group
226    }
227
228    // Get early reset group name from static control (we assume the static control
229    // is an enable).
230    fn get_reset_group_name(&self, sc: &mut ir::StaticControl) -> &ir::Id {
231        // assume that there are only static enables left.
232        // if there are any other type of static control, then error out.
233        let ir::StaticControl::Enable(s) = sc else {
234            unreachable!(
235                "Non-Enable Static Control should have been compiled away. Run {} to do this",
236                crate::passes::StaticInliner::name()
237            );
238        };
239
240        let sgroup = s.group.borrow_mut();
241        let sgroup_name = sgroup.name();
242        // get the "early reset group". It should exist, since we made an
243        // early_reset group for every static group in the component
244
245        (self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
246            unreachable!("group {} not in self.reset_early_map", sgroup_name)
247        })) as _
248    }
249}
250
251// These are the functions used to allocate FSMs to static islands through a
252// greedy coloring algorithm.
253impl CompileStatic {
254    // Given a list of `static_groups`, find the group named `name`.
255    // If there is no such group, then there is an unreachable! error.
256    fn find_static_group(
257        name: &ir::Id,
258        static_groups: &[ir::RRC<ir::StaticGroup>],
259    ) -> ir::RRC<ir::StaticGroup> {
260        Rc::clone(
261            static_groups
262                .iter()
263                .find(|static_group| static_group.borrow().name() == name)
264                .unwrap_or_else(|| {
265                    unreachable!("couldn't find static group {name}")
266                }),
267        )
268    }
269
270    /// Add conflicts between all nodes of `fsm_trees` which are executing
271    /// on separate threads of a dynamic `par` block.
272    /// This function adds conflicts between nodes of separate trees.
273    fn add_par_conflicts(
274        c: &ir::Control,
275        fsm_trees: &Vec<Node>,
276        conflict_graph: &mut GraphColoring<ir::Id>,
277    ) {
278        match c {
279            ir::Control::Empty(_)
280            | ir::Control::Enable(_)
281            | ir::Control::Invoke(_)
282            | ir::Control::Static(_) => (),
283            ir::Control::Seq(seq) => {
284                for stmt in &seq.stmts {
285                    Self::add_par_conflicts(stmt, fsm_trees, conflict_graph);
286                }
287            }
288            ir::Control::Repeat(ir::Repeat { body, .. })
289            | ir::Control::While(ir::While { body, .. }) => {
290                Self::add_par_conflicts(body, fsm_trees, conflict_graph)
291            }
292            ir::Control::If(if_stmt) => {
293                Self::add_par_conflicts(
294                    &if_stmt.tbranch,
295                    fsm_trees,
296                    conflict_graph,
297                );
298                Self::add_par_conflicts(
299                    &if_stmt.fbranch,
300                    fsm_trees,
301                    conflict_graph,
302                );
303            }
304            ir::Control::Par(par) => {
305                // `sgroup_conflict_vec` is a vec of HashSets.
306                // Each item in the vec corresponds to a par thread, and holds
307                // all of the groups executed in that thread.
308                let mut sgroup_conflict_vec: Vec<HashSet<ir::Id>> = Vec::new();
309                for stmt in &par.stmts {
310                    sgroup_conflict_vec.push(HashSet::from_iter(
311                        Self::get_static_enables(stmt),
312                    ));
313                }
314                for (thread1_st_enables, thread2_st_enables) in
315                    sgroup_conflict_vec.iter().tuple_combinations()
316                {
317                    // For each static group g1 enabled in thread1 and static
318                    // group g2 enabled in thread2 respectively, add a conflict
319                    // each node in g1 and g2's corresponding trees.
320                    for static_enable1 in thread1_st_enables {
321                        for static_enable2 in thread2_st_enables {
322                            // Getting tree1
323                            let tree1 = fsm_trees
324                                .iter()
325                                .find(|tree| {
326                                    tree.get_group_name() == static_enable1
327                                })
328                                .expect("couldn't find FSM tree");
329                            // Getting tree2
330                            let tree2 = fsm_trees
331                                .iter()
332                                .find(|tree| {
333                                    tree.get_group_name() == static_enable2
334                                })
335                                .expect("couldn't find tree");
336                            // Add conflict between each node in tree1 and tree2
337                            for sgroup1 in tree1.get_all_nodes() {
338                                for sgroup2 in tree2.get_all_nodes() {
339                                    conflict_graph
340                                        .insert_conflict(&sgroup1, &sgroup2)
341                                }
342                            }
343                        }
344                    }
345                }
346                // Necessary to add conflicts between nested pars
347                for stmt in &par.stmts {
348                    Self::add_par_conflicts(stmt, fsm_trees, conflict_graph);
349                }
350            }
351            ir::Control::FSMEnable(_) => {
352                todo!("should not encounter fsm nodes")
353            }
354        }
355    }
356
357    // Gets the maximum number of repeats for the static group named
358    // `sgroup` among all trees in `tree_objects`. Most of the time, `sgroup`
359    // will only appear once but it is possible that the same group appears
360    // in more than one tree.
361    fn get_max_num_repeats(sgroup: ir::Id, tree_objects: &Vec<Node>) -> u64 {
362        let mut cur_max = 1;
363        for tree in tree_objects {
364            cur_max = std::cmp::max(
365                cur_max,
366                tree.get_max_value(&sgroup, &(|tree| tree.num_repeats)),
367            )
368        }
369        cur_max
370    }
371
372    // Gets the maximum number of repeats for the static group named
373    // `sgroup` among all trees in `tree_objects`. Most of the time, `sgroup`
374    // will only appear once but it is possible that the same group appears
375    // in more than one tree.
376    fn get_max_num_states(sgroup: ir::Id, tree_objects: &Vec<Node>) -> u64 {
377        let mut cur_max = 1;
378        for tree in tree_objects {
379            cur_max = std::cmp::max(
380                cur_max,
381                tree.get_max_value(&sgroup, &(|tree| tree.num_states)),
382            )
383        }
384        cur_max
385    }
386
387    /// Creates a graph (one node per item in `sgroup` where nodes are the `sgroup`'s
388    /// names).
389    /// Use `tree_objects` and `control` to draw conflicts between any two nodes
390    /// that could be executing in parallel, and returns a greedy coloring of the
391    /// graph.
392    pub fn get_coloring(
393        &self,
394        tree_objects: &Vec<Node>,
395        sgroups: &[ir::RRC<ir::StaticGroup>],
396        control: &mut ir::Control,
397    ) -> HashMap<ir::Id, ir::Id> {
398        if !self.greedy_share {
399            // If !greedy_share just give each sgroup its own color.
400            return sgroups
401                .iter()
402                .map(|g| (g.borrow().name(), g.borrow().name()))
403                .collect();
404        }
405        let mut conflict_graph: GraphColoring<ir::Id> =
406            GraphColoring::from(sgroups.iter().map(|g| g.borrow().name()));
407
408        // Necessary conflicts to ensure correctness
409
410        // Self::add_par_conflicts adds necessary conflicts between all nodes of
411        // trees that execute in separate threads of the same `par` block: this is
412        // adding conflicts between nodes of separate trees.
413        Self::add_par_conflicts(control, tree_objects, &mut conflict_graph);
414        for tree in tree_objects {
415            // tree.add_conflicts adds the necessary conflicts within nodes of
416            // same tree.
417            tree.add_conflicts(&mut conflict_graph);
418        }
419        // Optional conflicts to ?potentially? improve QoR
420        // for (sgroup1, sgroup2) in sgroups.iter().tuple_combinations() {
421        //     let max_num_states1 =
422        //         Self::get_max_num_states(sgroup1.borrow().name(), tree_objects);
423        //     let max_num_repeats1 = Self::get_max_num_repeats(
424        //         sgroup1.borrow().name(),
425        //         tree_objects,
426        //     );
427        //     let max_num_states2 =
428        //         Self::get_max_num_states(sgroup2.borrow().name(), tree_objects);
429        //     let max_num_repeats2 = Self::get_max_num_repeats(
430        //         sgroup2.borrow().name(),
431        //         tree_objects,
432        //     );
433        //     if ((max_num_states1 == 1) != (max_num_states2 == 1))
434        //         || ((max_num_repeats1) != (max_num_repeats2))
435        //     {
436        //         conflict_graph.insert_conflict(
437        //             &sgroup1.borrow().name(),
438        //             &sgroup2.borrow().name(),
439        //         );
440        //     }
441        // }
442
443        conflict_graph.color_greedy(None, true)
444    }
445
446    /// Given a coloring of group names, returns a Hashmap that maps:
447    /// colors -> (max num states for that color, max num repeats for color).
448    pub fn get_color_max_values(
449        coloring: &HashMap<ir::Id, ir::Id>,
450        tree_objects: &Vec<Node>,
451    ) -> HashMap<ir::Id, (u64, u64)> {
452        let mut colors_to_sgroups: HashMap<ir::Id, Vec<ir::Id>> =
453            HashMap::new();
454        // "Reverse" the coloring: instead of maping group names->colors,
455        // map colors -> group names.
456        for (group_name, color) in coloring {
457            colors_to_sgroups
458                .entry(*color)
459                .or_default()
460                .push(*group_name);
461        }
462        colors_to_sgroups
463            .into_iter()
464            .map(|(name, colors_sgroups)| {
465                // Get max num states for this color
466                let max_num_states = colors_sgroups
467                    .iter()
468                    .map(|gname| Self::get_max_num_states(*gname, tree_objects))
469                    .max()
470                    .expect("color is empty");
471                // Get max num repeats for this color
472                let max_num_repeats = colors_sgroups
473                    .iter()
474                    .map(|gname| {
475                        Self::get_max_num_repeats(*gname, tree_objects)
476                    })
477                    .max()
478                    .expect("color is empty");
479                (name, (max_num_states, max_num_repeats))
480            })
481            .collect()
482    }
483}
484
485impl CompileStatic {
486    /// `get_interval_from_guard` returns the interval found within guard `g`.
487    /// The tricky part is that sometimes there can be an implicit latency
488    /// `lat` that is not explicitly stated (i.e., every assignment in a
489    /// group with latency n has an implicit guard of %[0:n]). `lat` is `n`.
490    fn get_interval_from_guard(
491        g: &ir::Guard<ir::StaticTiming>,
492        lat: u64,
493    ) -> (u64, u64) {
494        match g {
495            calyx_ir::Guard::Info(static_timing_interval) => {
496                static_timing_interval.get_interval()
497            }
498            calyx_ir::Guard::Not(_)
499            | calyx_ir::Guard::CompOp(_, _, _)
500            | calyx_ir::Guard::Port(_)
501            | calyx_ir::Guard::True => (0, lat),
502            calyx_ir::Guard::And(l, r) => {
503                let ((beg1, end1), (beg2, end2)) = (
504                    Self::get_interval_from_guard(l, lat),
505                    Self::get_interval_from_guard(r, lat),
506                );
507                assert!(end1 - beg1 == lat || end2 - beg2 == lat);
508                if end1 - beg1 == lat {
509                    (beg2, end2)
510                } else {
511                    (beg1, end1)
512                }
513            }
514            ir::Guard::Or(_, _) => unreachable!(
515                "Shouldn't try to get interval from guard if there is an 'or' in the guard"
516            ),
517        }
518    }
519
520    // Given a children_sched (a sorted vec of intervals for which
521    // the children are active), builds an FSM schedule and returns it,
522    // along with the number of states the resulting FSM will have (42 in the
523    // example given below).
524    // Schedule maps cycles (i,j) -> fsm state type (i.e., what the fsm outputs).
525    // Here is an example FSM schedule:
526    //                           Cycles     FSM State (i.e., `fsm.out`)
527    //                           (0..10) ->  Normal[0,10) // FSM counting from 0..10
528    //                           (10..30) -> Offload(10) // Offloading to child
529    //                           (30..40) -> Normal[11, 21)
530    //                           (40,80) ->  Offload(21)
531    //                           (80,100)->  Normal[22, 42)
532    //
533    // `target_latency` is the latency of the entire tree (100 in this case).
534    fn build_tree_schedule(
535        children_sched: &[(u64, u64)],
536        target_latency: u64,
537    ) -> (BTreeMap<(u64, u64), StateType>, u64) {
538        let mut fsm_schedule = BTreeMap::new();
539        let mut cur_num_states = 0;
540        let mut cur_lat = 0;
541        for (beg, end) in children_sched {
542            // Filling in the gap between children, if necessary with a
543            // `Normal` StateType.
544            if cur_lat != *beg {
545                fsm_schedule.insert(
546                    (cur_lat, *beg),
547                    StateType::Normal((
548                        cur_num_states,
549                        cur_num_states + (beg - cur_lat),
550                    )),
551                );
552                cur_num_states += beg - cur_lat;
553                // cur_lat = *beg; assignment is unnecessary
554            }
555            // Inserting an Offload StateType to the schedule.
556            fsm_schedule
557                .insert((*beg, *end), StateType::Offload(cur_num_states));
558            cur_lat = *end;
559            cur_num_states += 1;
560        }
561        // Filling in the gap between the final child and the end of the group
562        // with a Normal StateType.
563        if cur_lat != target_latency {
564            fsm_schedule.insert(
565                (cur_lat, target_latency),
566                StateType::Normal((
567                    cur_num_states,
568                    cur_num_states + (target_latency - cur_lat),
569                )),
570            );
571            cur_num_states += target_latency - cur_lat;
572        }
573        (fsm_schedule, cur_num_states)
574    }
575
576    /// Given a static group `target_name` and vec of `static_groups`, builds a
577    /// `tree_object` for group `target_name` that repeats itself `num_repeat`
578    /// times.
579    fn build_tree_object(
580        target_name: ir::Id,
581        static_groups: &[ir::RRC<ir::StaticGroup>],
582        num_repeats: u64,
583    ) -> Node {
584        // Find the group that will serve as the root of the tree.
585        let target_group = static_groups
586            .iter()
587            .find(|sgroup| sgroup.borrow().name() == target_name)
588            .unwrap();
589        // Children of the root of the tree.
590        let mut children_vec = vec![];
591
592        let target_group_ref = target_group.borrow();
593        for assign in &target_group_ref.assignments {
594            // Looking for static_child[go] = %[i:j] ? 1'd1; to build children.
595            // This lets us know that `static_child` is executing from cycles
596            // i through j.
597            match &assign.dst.borrow().parent {
598                PortParent::Cell(_) => {
599                    if target_group_ref.attributes.has(ir::BoolAttr::ParCtrl) {
600                        panic!("")
601                    }
602                }
603                PortParent::Group(_) | PortParent::FSM(_) => panic!(""),
604                PortParent::StaticGroup(sgroup) => {
605                    assert!(assign.src.borrow().is_constant(1, 1));
606                    let (beg, end) = Self::get_interval_from_guard(
607                        &assign.guard,
608                        target_group.borrow().get_latency(),
609                    );
610                    let name: calyx_ir::Id = sgroup.upgrade().borrow().name();
611                    // Need the following lines to determine `num_repeats`
612                    // for the child.
613                    let target_child_latency =
614                        Self::get_sgroup_latency(name, static_groups);
615                    let child_execution_time = end - beg;
616                    assert!(
617                        child_execution_time % target_child_latency == 0,
618                        "child will execute only part of an iteration"
619                    );
620                    let child_num_repeats =
621                        child_execution_time / target_child_latency;
622                    // Recursively build a tree for the child.
623                    children_vec.push((
624                        Self::build_tree_object(
625                            name,
626                            static_groups,
627                            child_num_repeats,
628                        ),
629                        (beg, end),
630                    ));
631                }
632            }
633        }
634
635        if target_group_ref.attributes.has(ir::BoolAttr::ParCtrl) {
636            // If we are in a par group, then the "children" are actually
637            // threads that should all start at 0.
638            assert!(children_vec.iter().all(|(_, (beg, _))| *beg == 0));
639            Node::Par(ParNodes {
640                group_name: target_name,
641                threads: children_vec,
642                latency: target_group_ref.latency,
643                num_repeats,
644            })
645        } else {
646            // If we are in a regular group, then the children should be
647            // non-overlapping.
648            children_vec.sort_by_key(|(_, interval)| *interval);
649            assert!(Self::are_ranges_non_overlapping(&children_vec));
650            let (fsm_schedule, num_states) = Self::build_tree_schedule(
651                &children_vec
652                    .iter()
653                    .map(|(_, interval)| *interval)
654                    .collect_vec(),
655                target_group_ref.latency,
656            );
657            Node::Single(SingleNode {
658                latency: target_group_ref.latency,
659                fsm_cell: None,
660                iter_count_cell: None,
661                root: (target_name, vec![]),
662                fsm_schedule,
663                children: children_vec,
664                num_repeats,
665                num_states,
666            })
667        }
668    }
669
670    /// Builds a dummy tree, solely for the purposes of determining conflicts
671    /// so we can greedily color when assigning FSMs. This should only be occuring
672    /// when we count during offloading (i.e., don't pause).
673    /// This tree should never actually be turned into hardware!! (Thd trees that we
674    /// build here do not make sense if you want to actually do that.)
675    /// We can't call `build_tree_object` on this because that looks for the
676    /// `par` attribute, which isn't present when we're counting.
677    fn build_dummy_tree(
678        target_name: ir::Id,
679        static_groups: &[ir::RRC<ir::StaticGroup>],
680    ) -> Node {
681        // Find the group that will serve as the root of the tree.
682        let target_group = static_groups
683            .iter()
684            .find(|sgroup| sgroup.borrow().name() == target_name)
685            .unwrap();
686        let mut children_vec = vec![];
687        let target_group_ref = target_group.borrow();
688        assert!(
689            !target_group_ref.attributes.has(ir::BoolAttr::ParCtrl),
690            "ParCtrl attribute is not compatible with building dummy trees"
691        );
692        for assign in &target_group_ref.assignments {
693            // Looking for static_child[go] = %[i:j] ? 1'd1; to build children.
694            match &assign.dst.borrow().parent {
695                PortParent::Cell(_) => (),
696                PortParent::Group(_) | PortParent::FSM(_) => unreachable!(""),
697                PortParent::StaticGroup(sgroup) => {
698                    assert!(assign.src.borrow().is_constant(1, 1));
699                    let (beg, end) = Self::get_interval_from_guard(
700                        &assign.guard,
701                        target_group.borrow().get_latency(),
702                    );
703
704                    let name: calyx_ir::Id = sgroup.upgrade().borrow().name();
705                    children_vec.push((
706                        Self::build_dummy_tree(name, static_groups),
707                        (beg, end),
708                    ));
709                }
710            }
711        }
712
713        children_vec.sort_by_key(|(_, interval)| *interval);
714        Node::Single(SingleNode {
715            latency: target_group_ref.latency,
716            fsm_cell: None,
717            iter_count_cell: None,
718            root: (target_name, vec![]),
719            fsm_schedule: BTreeMap::new(),
720            children: children_vec,
721            num_repeats: 1,
722            num_states: target_group_ref.latency,
723        })
724    }
725
726    /// Builds "trees" but just make them single nodes that never offload.
727    /// This is the original strategy that we used.
728    fn build_single_node(
729        name: ir::Id,
730        static_groups: &[ir::RRC<ir::StaticGroup>],
731    ) -> Node {
732        let target_group = static_groups
733            .iter()
734            .find(|sgroup| sgroup.borrow().name() == name)
735            .unwrap();
736        let target_group_ref = target_group.borrow();
737        assert!(
738            !target_group_ref.attributes.has(ir::BoolAttr::ParCtrl),
739            "ParCtrl attribute is not compatible with building a single node"
740        );
741
742        Node::Single(SingleNode {
743            latency: target_group_ref.latency,
744            fsm_cell: None,
745            iter_count_cell: None,
746            root: (name, vec![]),
747            fsm_schedule: vec![(
748                (0, target_group_ref.latency),
749                StateType::Normal((0, target_group_ref.latency)),
750            )]
751            .into_iter()
752            .collect(),
753            children: vec![],
754            num_repeats: 1,
755            num_states: target_group_ref.latency,
756        })
757    }
758
759    /// Search through `static_groups` and get latency of sgroup named `name`
760    fn get_sgroup_latency(
761        name: ir::Id,
762        static_groups: &[ir::RRC<ir::StaticGroup>],
763    ) -> u64 {
764        static_groups
765            .iter()
766            .find(|sgroup| sgroup.borrow().name() == name)
767            .expect("couldn't find static group")
768            .borrow()
769            .get_latency()
770    }
771
772    // Given a vec of tuples (i,j) sorted by the first element (i.e., `i`) checks
773    // whether the ranges do not overlap.
774    fn are_ranges_non_overlapping(ranges: &[(Node, (u64, u64))]) -> bool {
775        if ranges.is_empty() {
776            return true;
777        }
778        for i in 0..ranges.len() - 1 {
779            let (_, (_, end1)) = ranges[i];
780            let (_, (start2, _)) = ranges[i + 1];
781            // Ensure that the current range's end is less than or equal to the next range's start
782            if end1 > start2 {
783                return false;
784            }
785        }
786        true
787    }
788
789    // Get a vec of all static groups that were "enabled" in `ctrl`.
790    fn get_static_enables(ctrl: &ir::Control) -> Vec<ir::Id> {
791        match ctrl {
792            ir::Control::Seq(ir::Seq { stmts, .. })
793            | ir::Control::Par(ir::Par { stmts, .. }) => stmts
794                .iter()
795                .flat_map(Self::get_static_enables)
796                .collect_vec(),
797            ir::Control::Empty(_)
798            | ir::Control::Enable(_)
799            | ir::Control::Invoke(_) => vec![],
800            ir::Control::If(c) => {
801                let mut tbranch_res = Self::get_static_enables(&c.tbranch);
802                let fbranch_res = Self::get_static_enables(&c.fbranch);
803                tbranch_res.extend(fbranch_res);
804                tbranch_res
805            }
806            ir::Control::Repeat(ir::Repeat { body, .. })
807            | ir::Control::While(ir::While { body, .. }) => {
808                Self::get_static_enables(body)
809            }
810            ir::Control::Static(sc) => {
811                let ir::StaticControl::Enable(s) = sc else {
812                    unreachable!(
813                        "Non-Enable Static Control should have been compiled away. Run {} to do this",
814                        crate::passes::StaticInliner::name()
815                    );
816                };
817                vec![s.group.borrow().name()]
818            }
819            ir::Control::FSMEnable(_) => {
820                todo!("should not encounter fsm nodes")
821            }
822        }
823    }
824}
825
826// These are the functions used to compile for the static *component* interface,
827// which (annoyingly) only needs a go signal fro %0, compared to %[0:n] for
828// static groups.
829impl CompileStatic {
830    // Used for guards in a one cycle static component.
831    // Replaces %0 with comp.go.
832    fn make_guard_dyn_one_cycle_static_comp(
833        guard: ir::Guard<ir::StaticTiming>,
834        comp_sig: RRC<ir::Cell>,
835    ) -> ir::Guard<ir::Nothing> {
836        match guard {
837            ir::Guard::Or(l, r) => {
838                let left = Self::make_guard_dyn_one_cycle_static_comp(
839                    *l,
840                    Rc::clone(&comp_sig),
841                );
842                let right = Self::make_guard_dyn_one_cycle_static_comp(
843                    *r,
844                    Rc::clone(&comp_sig),
845                );
846                ir::Guard::or(left, right)
847            }
848            ir::Guard::And(l, r) => {
849                let left = Self::make_guard_dyn_one_cycle_static_comp(
850                    *l,
851                    Rc::clone(&comp_sig),
852                );
853                let right = Self::make_guard_dyn_one_cycle_static_comp(
854                    *r,
855                    Rc::clone(&comp_sig),
856                );
857                ir::Guard::and(left, right)
858            }
859            ir::Guard::Not(g) => {
860                let f = Self::make_guard_dyn_one_cycle_static_comp(
861                    *g,
862                    Rc::clone(&comp_sig),
863                );
864                ir::Guard::Not(Box::new(f))
865            }
866            ir::Guard::Info(t) => match t.get_interval() {
867                (0, 1) => guard!(comp_sig["go"]),
868                _ => unreachable!(
869                    "This function is implemented for 1 cycle static components, only %0 can exist as timing guard"
870                ),
871            },
872            ir::Guard::CompOp(op, l, r) => ir::Guard::CompOp(op, l, r),
873            ir::Guard::Port(p) => ir::Guard::Port(p),
874            ir::Guard::True => ir::Guard::True,
875        }
876    }
877
878    // Used for assignments in a one cycle static component.
879    // Replaces %0 with comp.go in the assignment's guard.
880    fn make_assign_dyn_one_cycle_static_comp(
881        assign: ir::Assignment<ir::StaticTiming>,
882        comp_sig: RRC<ir::Cell>,
883    ) -> ir::Assignment<ir::Nothing> {
884        ir::Assignment {
885            src: assign.src,
886            dst: assign.dst,
887            attributes: assign.attributes,
888            guard: Box::new(Self::make_guard_dyn_one_cycle_static_comp(
889                *assign.guard,
890                comp_sig,
891            )),
892        }
893    }
894
895    // Makes `done` signal for promoted static<n> component.
896    fn make_done_signal_for_promoted_component(
897        fsm_tree: &mut Node,
898        builder: &mut ir::Builder,
899        comp_sig: RRC<ir::Cell>,
900    ) -> Vec<ir::Assignment<ir::Nothing>> {
901        let first_state_guard = fsm_tree.query_between((0, 1), builder);
902        structure!(builder;
903          let sig_reg = prim std_reg(1);
904          let one = constant(1, 1);
905          let zero = constant(0, 1);
906        );
907        let go_guard = guard!(comp_sig["go"]);
908        let not_go_guard = !guard!(comp_sig["go"]);
909        let comp_done_guard =
910            first_state_guard.clone().and(guard!(sig_reg["out"]));
911        let assigns = build_assignments!(builder;
912          // Only write to sig_reg when fsm == 0
913          sig_reg["write_en"] = first_state_guard ? one["out"];
914          // If fsm == 0 and comp.go is high, it means we are starting an execution,
915          // so we set signal_reg to high. Note that this happens regardless of
916          // whether comp.done is high.
917          sig_reg["in"] = go_guard ? one["out"];
918          // Otherwise, we set `sig_reg` to low.
919          sig_reg["in"] = not_go_guard ? zero["out"];
920          // comp.done is high when FSM == 0 and sig_reg is high,
921          // since that means we have just finished an execution.
922          comp_sig["done"] = comp_done_guard ? one["out"];
923        );
924        assigns.to_vec()
925    }
926
927    // Makes a done signal for a one-cycle static component.
928    // Essentially you just have to use a one-cycle delay register that
929    // takes the `go` signal as input.
930    fn make_done_signal_for_promoted_component_one_cycle(
931        builder: &mut ir::Builder,
932        comp_sig: RRC<ir::Cell>,
933    ) -> Vec<ir::Assignment<ir::Nothing>> {
934        structure!(builder;
935          let sig_reg = prim std_reg(1);
936          let one = constant(1, 1);
937          let zero = constant(0, 1);
938        );
939        let go_guard = guard!(comp_sig["go"]);
940        let not_go = !guard!(comp_sig["go"]);
941        let signal_on_guard = guard!(sig_reg["out"]);
942        let assigns = build_assignments!(builder;
943          // For one cycle components, comp.done is just whatever comp.go
944          // was during the previous cycle.
945          // signal_reg serves as a forwarding register that delays
946          // the `go` signal for one cycle.
947          sig_reg["in"] = go_guard ? one["out"];
948          sig_reg["in"] = not_go ? zero["out"];
949          sig_reg["write_en"] = ? one["out"];
950          comp_sig["done"] = signal_on_guard ? one["out"];
951        );
952        assigns.to_vec()
953    }
954
955    // `fsm_tree` should be the top-level tree in the components.
956    // `static_groups` are the component's static groups.
957    // The assignments are removed from `sgroup` and placed into
958    // `builder.component`'s continuous assignments.
959    fn compile_static_interface(
960        &mut self,
961        fsm_tree: &mut Node,
962        static_groups: &mut Vec<ir::RRC<ir::StaticGroup>>,
963        coloring: &HashMap<ir::Id, ir::Id>,
964        colors_to_max_values: &HashMap<ir::Id, (u64, u64)>,
965        colors_to_fsm: &mut HashMap<
966            ir::Id,
967            (OptionalStaticFSM, OptionalStaticFSM),
968        >,
969        builder: &mut ir::Builder,
970    ) -> calyx_utils::CalyxResult<()> {
971        if fsm_tree.get_latency() > 1 {
972            // Find top-level static group.
973            let sgroup = Self::find_static_group(
974                &fsm_tree.get_root_name(),
975                static_groups,
976            );
977            // Perform some preprocessing on the assignments
978            // (in particular, transform %[0:n] into %0 | %[1:n])
979            for assign in &mut sgroup.borrow_mut().assignments {
980                Node::preprocess_static_interface_assigns(
981                    assign,
982                    Rc::clone(&builder.component.signature),
983                );
984            }
985
986            let comp_go = ir::Guard::port(
987                builder
988                    .component
989                    .signature
990                    .borrow()
991                    .find_unique_with_attr(ir::NumAttr::Go)?
992                    .unwrap(),
993            );
994
995            // Realize the fsm tree in hardware.
996            fsm_tree.instantiate_fsms(
997                builder,
998                coloring,
999                colors_to_max_values,
1000                colors_to_fsm,
1001                self.one_hot_cutoff,
1002            );
1003            fsm_tree.count_to_n(builder, Some(comp_go));
1004            fsm_tree.realize(
1005                false,
1006                static_groups,
1007                &mut self.reset_early_map,
1008                &mut self.fsm_info_map,
1009                &mut self.group_rewrites,
1010                builder,
1011            );
1012            // Add root's assignments as continuous assignments, execpt for the
1013            // `group[done]` assignments.
1014            builder.component.continuous_assignments.extend(
1015                fsm_tree.take_root_assigns().into_iter().filter(|assign| {
1016                    let dst = assign.dst.borrow();
1017                    match dst.parent {
1018                        PortParent::Cell(_) => true,
1019                        // Don't add assignment to `group[done]`
1020                        PortParent::Group(_) => dst.name != "done",
1021                        PortParent::StaticGroup(_) => true,
1022                        PortParent::FSM(_) => unreachable!(),
1023                    }
1024                }),
1025            );
1026            let comp_sig = Rc::clone(&builder.component.signature);
1027            if builder.component.attributes.has(ir::BoolAttr::Promoted) {
1028                // If necessary, add the logic to produce a done signal.
1029                let done_assigns =
1030                    Self::make_done_signal_for_promoted_component(
1031                        fsm_tree, builder, comp_sig,
1032                    );
1033                builder
1034                    .component
1035                    .continuous_assignments
1036                    .extend(done_assigns);
1037            }
1038        } else {
1039            // Handle components with latency == 1.
1040            // In this case, we don't need an FSM; we just guard the assignments
1041            // with comp.go.
1042            let sgroup = Self::find_static_group(
1043                &fsm_tree.get_root_name(),
1044                static_groups,
1045            );
1046            for (child, _) in fsm_tree.get_children() {
1047                // We can ignore any static timing guards in the children,
1048                // since we know the latency is 1. That is why we call
1049                // `convert_assignments_type`.
1050                child.realize(
1051                    true,
1052                    static_groups,
1053                    &mut self.reset_early_map,
1054                    &mut self.fsm_info_map,
1055                    &mut self.group_rewrites,
1056                    builder,
1057                )
1058            }
1059            let assignments =
1060                std::mem::take(&mut sgroup.borrow_mut().assignments);
1061            for mut assign in assignments {
1062                // Make `assignments` continuous and replace %[0:1] with `comp.go`
1063                let comp_sig = Rc::clone(&builder.component.signature);
1064                assign.guard.update(|g| g.and(guard!(comp_sig["go"])));
1065                builder.component.continuous_assignments.push(
1066                    Self::make_assign_dyn_one_cycle_static_comp(
1067                        assign, comp_sig,
1068                    ),
1069                );
1070            }
1071            if builder.component.attributes.has(ir::BoolAttr::Promoted) {
1072                // Need to add a done signal if this component was promoted.
1073                let comp_sig = Rc::clone(&builder.component.signature);
1074                let done_assigns =
1075                    Self::make_done_signal_for_promoted_component_one_cycle(
1076                        builder, comp_sig,
1077                    );
1078                builder
1079                    .component
1080                    .continuous_assignments
1081                    .extend(done_assigns);
1082            };
1083        };
1084        Ok(())
1085    }
1086}
1087
1088impl Visitor for CompileStatic {
1089    fn start(
1090        &mut self,
1091        comp: &mut ir::Component,
1092        sigs: &ir::LibrarySignatures,
1093        _comps: &[ir::Component],
1094    ) -> VisResult {
1095        // Drain static groups of component
1096        let mut sgroups: Vec<ir::RRC<ir::StaticGroup>> =
1097            comp.get_static_groups_mut().drain().collect();
1098
1099        let mut builder = ir::Builder::new(comp, sigs);
1100        // Get a vec of all groups that are enabled in comp's control.
1101        let static_enable_ids =
1102            Self::get_static_enables(&builder.component.control.borrow());
1103        // Build one tree object per static enable.
1104        // Even if we don't offload, we still need to build trees to
1105        // determine coloring.
1106        let default_tree_objects = static_enable_ids
1107            .iter()
1108            .map(|id| {
1109                if self.offload_pause {
1110                    Self::build_tree_object(*id, &sgroups, 1)
1111                } else {
1112                    // If we're not offloading, then we should build dummy trees
1113                    // that are just used to determine coloring.
1114                    // This is basically the same thing as `build_tree_object`,
1115                    // but doesn't check the `ParCtrl` attribute. I think
1116                    // we could reduce code size by merging this function with
1117                    // `build_tree_object`.
1118                    Self::build_dummy_tree(*id, &sgroups)
1119                }
1120            })
1121            .collect_vec();
1122
1123        // The first thing is to assign FSMs -> static islands.
1124        // We sometimes assign the same FSM to different static islands
1125        // to reduce register usage. We do this by getting greedy coloring.
1126        let coloring: HashMap<ir::Id, ir::Id> = self.get_coloring(
1127            &default_tree_objects,
1128            &sgroups,
1129            &mut builder.component.control.borrow_mut(),
1130        );
1131        // We need the max_num_states  and max_num_repeats for each
1132        // color so we know how many bits the corresponding registers should get.
1133        let colors_to_max_values =
1134            Self::get_color_max_values(&coloring, &default_tree_objects);
1135        let mut colors_to_fsms: HashMap<
1136            ir::Id,
1137            (OptionalStaticFSM, OptionalStaticFSM),
1138        > = HashMap::new();
1139
1140        let mut tree_objects = if self.offload_pause {
1141            default_tree_objects
1142        } else {
1143            // Build simple trees if we're not offloading (i.e., build trees
1144            // that just consist of a single node.)
1145            // Note that these trees would not correctly draw conflicts between
1146            // nodes for coloring.
1147            let mut simple_trees = vec![];
1148            let sgroup_names = sgroups
1149                .iter()
1150                .map(|sgroup| sgroup.borrow().name())
1151                .collect_vec();
1152            for name in sgroup_names {
1153                simple_trees.push(Self::build_single_node(name, &sgroups))
1154            }
1155            simple_trees
1156        };
1157
1158        // Static components have a different interface than static groups.
1159        // If we have a static component, we have to compile the top-level
1160        // island (this island should be a group by now and corresponds
1161        // to the the entire control of the component) differently.
1162        // This island might invoke other static groups-- these static groups
1163        // should still follow the group interface.
1164        let top_level_sgroup = if builder.component.is_static() {
1165            let comp_control = builder.component.control.borrow();
1166            match &*comp_control {
1167                ir::Control::Static(ir::StaticControl::Enable(sen)) => {
1168                    Some(sen.group.borrow().name())
1169                }
1170                _ => {
1171                    return Err(Error::malformed_control(format!(
1172                        "Non-Enable Static Control should have been compiled away. Run {} to do this",
1173                        crate::passes::StaticInliner::name()
1174                    )));
1175                }
1176            }
1177        } else {
1178            None
1179        };
1180        // Make each tree count to n.
1181        for tree in &mut tree_objects {
1182            // Check whether we are compiling the top level static island.
1183            let static_component_interface = match top_level_sgroup {
1184                None => false,
1185                // For the top level group, sch.static_groups should really only
1186                // have one group--the top level group.
1187                Some(top_level_group) => {
1188                    tree.get_group_name() == top_level_group
1189                }
1190            };
1191            // Static component/groups have different interfaces
1192            if static_component_interface {
1193                // Compile top level static group differently.
1194                self.compile_static_interface(
1195                    tree,
1196                    &mut sgroups,
1197                    &coloring,
1198                    &colors_to_max_values,
1199                    &mut colors_to_fsms,
1200                    &mut builder,
1201                )?;
1202            } else {
1203                // Otherwise just instantiate the tree to hardware.
1204                tree.instantiate_fsms(
1205                    &mut builder,
1206                    &coloring,
1207                    &colors_to_max_values,
1208                    &mut colors_to_fsms,
1209                    self.one_hot_cutoff,
1210                );
1211                tree.count_to_n(&mut builder, None);
1212                tree.realize(
1213                    false,
1214                    &sgroups,
1215                    &mut self.reset_early_map,
1216                    &mut self.fsm_info_map,
1217                    &mut self.group_rewrites,
1218                    &mut builder,
1219                );
1220            }
1221        }
1222
1223        // Rewrite static_group[go] to early_reset_group[go]
1224        // don't have to worry about writing static_group[done] b/c static
1225        // groups don't have done holes.
1226        comp.for_each_assignment(|assign| {
1227            assign.for_each_port(|port| {
1228                self.group_rewrites
1229                    .get(&port.borrow().canonical())
1230                    .map(Rc::clone)
1231            })
1232        });
1233
1234        // Add the static groups back to the component.
1235        comp.get_static_groups_mut().append(sgroups.into_iter());
1236
1237        Ok(Action::Continue)
1238    }
1239
1240    /// Executed after visiting the children of a [ir::Static] node.
1241    fn start_static_control(
1242        &mut self,
1243        sc: &mut ir::StaticControl,
1244        comp: &mut ir::Component,
1245        sigs: &ir::LibrarySignatures,
1246        _comps: &[ir::Component],
1247    ) -> VisResult {
1248        // The main purpose of this method is inserting wrappers / singal registers
1249        // when appropriate.
1250
1251        // No need to build wrapper for static component interface
1252        if comp.is_static() {
1253            return Ok(Action::Continue);
1254        }
1255        // Assume that there are only static enables left.
1256        // If there are any other type of static control, then error out.
1257        let ir::StaticControl::Enable(s) = sc else {
1258            return Err(Error::malformed_control(format!(
1259                "Non-Enable Static Control should have been compiled away. Run {} to do this",
1260                crate::passes::StaticInliner::name()
1261            )));
1262        };
1263
1264        let sgroup = s.group.borrow_mut();
1265        let sgroup_name = sgroup.name();
1266        // get the "early reset group". It should exist, since we made an
1267        // early_reset group for every static group in the component
1268        let early_reset_name =
1269            self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
1270                unreachable!(
1271                    "{}'s early reset group has not been created",
1272                    sgroup_name
1273                )
1274            });
1275        // check if we've already built the wrapper group for early_reset_group
1276        // if so, we can just use that, otherwise, we must build the wrapper group
1277        let group_choice = match self.wrapper_map.get(early_reset_name) {
1278            None => {
1279                // create the builder/cells that we need to create wrapper group
1280                let mut builder = ir::Builder::new(comp, sigs);
1281                let (fsm_name, _, fsm_final_state) = self.fsm_info_map.get(early_reset_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", early_reset_name));
1282                // If we've already made a wrapper for a group that uses the same
1283                // FSM, we can reuse the signal_reg. Otherwise, we must
1284                // instantiate a new signal_reg.
1285                let wrapper = match self.signal_reg_map.get(fsm_name) {
1286                    None => {
1287                        // Need to build the signal_reg and the continuous
1288                        // assignment that resets the signal_reg
1289                        structure!( builder;
1290                            let signal_reg = prim std_reg(1);
1291                        );
1292                        self.signal_reg_map
1293                            .insert(*fsm_name, signal_reg.borrow().name());
1294                        Self::build_wrapper_group(
1295                            fsm_final_state.clone(),
1296                            early_reset_name,
1297                            signal_reg,
1298                            &mut builder,
1299                            true,
1300                        )
1301                    }
1302                    Some(reg_name) => {
1303                        // Already_built the signal_reg.
1304                        // We don't need to add continuous assignments
1305                        // that resets signal_reg.
1306                        let signal_reg = builder
1307                            .component
1308                            .find_cell(*reg_name)
1309                            .unwrap_or_else(|| {
1310                                unreachable!("signal reg {reg_name} found")
1311                            });
1312                        Self::build_wrapper_group(
1313                            fsm_final_state.clone(),
1314                            early_reset_name,
1315                            signal_reg,
1316                            &mut builder,
1317                            false,
1318                        )
1319                    }
1320                };
1321                self.wrapper_map
1322                    .insert(*early_reset_name, wrapper.borrow().name());
1323                wrapper
1324            }
1325            Some(name) => comp.find_group(*name).unwrap(),
1326        };
1327
1328        let mut e = ir::Control::enable(group_choice);
1329        let attrs = std::mem::take(&mut s.attributes);
1330        *e.get_mut_attributes() = attrs;
1331        Ok(Action::Change(Box::new(e)))
1332    }
1333
1334    /// If while body is static, then we want to make sure that the while
1335    /// body does not take the extra cycle incurred by the done condition
1336    /// So we replace the while loop with `enable` of a wrapper group
1337    /// that sets the go signal of the static group in the while loop body high
1338    /// (all static control should be compiled into static groups by
1339    /// `static_inliner` now). The done signal of the wrapper group should be
1340    /// the condition that the fsm of the while body is %0 and the port signal
1341    /// is 1'd0.
1342    /// For example, we replace
1343    /// ```
1344    /// wires {
1345    /// static group A<1> {
1346    ///     ...
1347    ///   }
1348    ///    ...
1349    /// }
1350    /// control {
1351    ///   while l.out {
1352    ///     A;
1353    ///   }
1354    /// }
1355    /// ```
1356    /// with
1357    /// ```
1358    /// wires {
1359    ///  group early_reset_A {
1360    ///     ...
1361    ///        }
1362    ///
1363    /// group while_wrapper_early_reset_A {
1364    ///       early_reset_A[go] = 1'd1;
1365    ///       while_wrapper_early_reset_A[done] = !l.out & fsm.out == 1'd0 ? 1'd1;
1366    ///     }
1367    ///   }
1368    ///   control {
1369    ///     while_wrapper_early_reset_A;
1370    ///   }
1371    /// ```
1372    fn start_while(
1373        &mut self,
1374        s: &mut ir::While,
1375        comp: &mut ir::Component,
1376        sigs: &ir::LibrarySignatures,
1377        _comps: &[ir::Component],
1378    ) -> VisResult {
1379        if s.cond.is_none() {
1380            if let ir::Control::Static(sc) = &mut *(s.body) {
1381                let mut builder = ir::Builder::new(comp, sigs);
1382                let reset_group_name = self.get_reset_group_name(sc);
1383
1384                // Get fsm for reset_group
1385                let (_, fsm_first_state, _) = self.fsm_info_map.get(reset_group_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", reset_group_name));
1386                let wrapper_group = self.build_wrapper_group_while(
1387                    fsm_first_state.clone(),
1388                    reset_group_name,
1389                    Rc::clone(&s.port),
1390                    &mut builder,
1391                );
1392                let c = ir::Control::enable(wrapper_group);
1393                return Ok(Action::change(c));
1394            }
1395        }
1396
1397        Ok(Action::Continue)
1398    }
1399
1400    fn finish(
1401        &mut self,
1402        comp: &mut ir::Component,
1403        _sigs: &ir::LibrarySignatures,
1404        _comps: &[ir::Component],
1405    ) -> VisResult {
1406        // make sure static groups have no assignments, since
1407        // we should have already drained the assignments in static groups
1408        // for g in comp.get_static_groups() {
1409        //     if !g.borrow().assignments.is_empty() {
1410        //         unreachable!("Should have converted all static groups to dynamic. {} still has assignments in it. It's possible that you may need to run {} to remove dead groups and get rid of this error.", g.borrow().name(), crate::passes::DeadGroupRemoval::name());
1411        //     }
1412        // }
1413        // remove all static groups
1414        comp.get_static_groups_mut().retain(|_| false);
1415
1416        // Remove control if static component
1417        if comp.is_static() {
1418            comp.control = ir::rrc(ir::Control::empty())
1419        }
1420
1421        Ok(Action::Continue)
1422    }
1423}