calyx_opt/passes/
static_repeat_fsm_allocation.rs

1use crate::analysis::{IncompleteTransition, StaticSchedule};
2use crate::traversal::{Action, ConstructVisitor, Named, Visitor};
3use calyx_ir::{self as ir, BoolAttr, GetAttributes};
4use calyx_utils::CalyxResult;
5use core::ops::Not;
6use itertools::Itertools;
7
8pub struct StaticRepeatFSMAllocation {}
9
10impl Named for StaticRepeatFSMAllocation {
11    fn name() -> &'static str {
12        "static-repeat-fsm-alloc"
13    }
14    fn description() -> &'static str {
15        "compiles a static repeat into an FSM construct"
16    }
17}
18
19impl ConstructVisitor for StaticRepeatFSMAllocation {
20    fn from(_ctx: &ir::Context) -> CalyxResult<Self> {
21        Ok(StaticRepeatFSMAllocation {})
22    }
23    fn clear_data(&mut self) {}
24}
25
26impl StaticSchedule<'_, '_> {
27    /// Provided a static control node, calling this method on an empty `StaticSchedule`
28    /// `sch` will build out the `latency` and `state2assigns` fields of `sch`, in
29    /// preparation to replace the `StaticControl` node with an instance of `ir::FSM`.
30    /// Every static assignment collected into `state2assigns` will have its existing guard
31    /// "anded" with `guard`.
32    fn build_abstract_fsm(
33        &mut self,
34        scon: &ir::StaticControl,
35        guard: ir::Guard<ir::Nothing>,
36        mut transitions_to_curr: Vec<IncompleteTransition>,
37    ) -> Vec<IncompleteTransition> {
38        match scon {
39            ir::StaticControl::Empty(_) => transitions_to_curr,
40            ir::StaticControl::Enable(sen) => {
41                // for all parts of the FSM that want to transition to this enable,
42                // register their transitions in self.state2trans
43                self.register_transitions(
44                    self.state,
45                    &mut transitions_to_curr,
46                    guard.clone(),
47                );
48
49                // allocate one state if requested, and have one state for every
50                // cycle otherwise
51                if sen.attributes.has(BoolAttr::OneState) {
52                    let final_state_guard =
53                        self.leave_one_state_condition(guard, sen);
54
55                    self.state += 1;
56                    vec![IncompleteTransition::new(
57                        self.state - 1,
58                        final_state_guard,
59                    )]
60                } else {
61                    sen.group.borrow().assignments.iter().for_each(|sassign| {
62                        sassign
63                            .guard
64                            .compute_live_states(sen.group.borrow().latency)
65                            .into_iter()
66                            .for_each(|offset| {
67                                // convert the static assignment to a normal one
68                                let mut assign: ir::Assignment<ir::Nothing> =
69                                    ir::Assignment::from(sassign.clone());
70                                // "and" the assignment's guard with argument guard
71                                assign.and_guard(guard.clone());
72                                // add this assignment to the list of assignments
73                                // that are supposed to be valid at this state
74                                self.state2assigns
75                                    .entry(self.state + offset)
76                                    .and_modify(|other_assigns| {
77                                        other_assigns.push(assign.clone())
78                                    })
79                                    .or_insert(vec![assign]);
80                            })
81                    });
82                    self.state += sen.group.borrow().latency;
83                    // Don't know where to transition next; let the parent that called
84                    // `build_abstract_fsm` deal with registering the transition from the state(s)
85                    // we just built.
86                    vec![IncompleteTransition::new(
87                        self.state - 1,
88                        ir::Guard::True,
89                    )]
90                }
91            }
92            ir::StaticControl::Seq(sseq) => sseq.stmts.iter().fold(
93                transitions_to_curr,
94                |transitions_to_this_stmt, stmt| {
95                    self.build_abstract_fsm(
96                        stmt,
97                        guard.clone(),
98                        transitions_to_this_stmt,
99                    )
100                },
101            ),
102
103            ir::StaticControl::If(sif) => {
104                // construct a guard on the static assignments in the each branch
105                let build_branch_guard =
106                    |is_true_branch: bool| -> ir::Guard<ir::Nothing> {
107                        guard.clone().and({
108                            if is_true_branch {
109                                ir::Guard::port(sif.port.clone())
110                            } else {
111                                ir::Guard::not(ir::Guard::port(
112                                    sif.port.clone(),
113                                ))
114                            }
115                        })
116                    };
117                self.build_abstract_fsm(
118                    &sif.tbranch,
119                    guard.clone().and(build_branch_guard(true)),
120                    transitions_to_curr.clone(),
121                )
122                .into_iter()
123                .chain(self.build_abstract_fsm(
124                    &sif.fbranch,
125                    guard.clone().and(build_branch_guard(false)),
126                    transitions_to_curr.clone(),
127                ))
128                .collect()
129            }
130            ir::StaticControl::Repeat(srep) => {
131                // unroll an encountered repeat loop. usually these are compiled away
132                (0..srep.num_repeats).fold(
133                    transitions_to_curr,
134                    |transitions_to_this_body, _| {
135                        self.build_abstract_fsm(
136                            &srep.body,
137                            guard.clone(),
138                            transitions_to_this_body,
139                        )
140                    },
141                )
142            }
143            ir::StaticControl::Par(_) => {
144                unreachable!(
145                    "`construct_schedule` encountered a `static_par` node. \
146              Should have been compiled into a static group."
147                )
148            }
149            ir::StaticControl::Invoke(_) => {
150                unreachable!(
151                    "`construct_schedule` encountered a `static_invoke` node. \
152              Should have been compiled away."
153                )
154            }
155        }
156    }
157
158    /// Returns the FSM implementing the given control node, as well as the buidler
159    /// object from which it was built.
160    fn build_fsm(&mut self, control: &ir::StaticControl) -> ir::RRC<ir::FSM> {
161        let fsm = self.builder.add_fsm("fsm");
162
163        let mut remaining_assignments =
164            self.build_abstract_fsm(control, ir::Guard::True, vec![]);
165
166        // add loopback transitions to first state
167        self.register_transitions(
168            0,
169            &mut remaining_assignments,
170            ir::Guard::True,
171        );
172
173        let (assignments, transitions, state2wires) =
174            self.build_fsm_pieces(ir::RRC::clone(&fsm));
175
176        self.builder.add_continuous_assignments(
177            self.state2assigns
178                .drain()
179                .flat_map(|(state, mut assigns)| {
180                    assigns.iter_mut().for_each(|assign| {
181                        assign.and_guard(ir::Guard::port(
182                            state2wires
183                                .get(state as usize)
184                                .unwrap()
185                                .borrow()
186                                .get("out"),
187                        ));
188                    });
189                    assigns
190                })
191                .collect(),
192        );
193
194        fsm.borrow_mut().extend_fsm(assignments, transitions);
195        fsm
196    }
197}
198
199impl Visitor for StaticRepeatFSMAllocation {
200    fn finish_static_if(
201        &mut self,
202        s: &mut calyx_ir::StaticIf,
203        comp: &mut calyx_ir::Component,
204        sigs: &calyx_ir::LibrarySignatures,
205        _comps: &[calyx_ir::Component],
206    ) -> crate::traversal::VisResult {
207        let mut builder = ir::Builder::new(comp, sigs);
208        let signal_on = builder.add_constant(1, 1);
209
210        // generate FSM for true branch
211        let mut sch_constructor_true = StaticSchedule::from(&mut builder);
212        let true_branch_fsm = sch_constructor_true.build_fsm(&s.tbranch);
213
214        // group to active each FSM conditionally
215        let if_group = builder.add_static_group("if", s.latency);
216        let true_guard: ir::Guard<ir::StaticTiming> =
217            ir::Guard::port(ir::RRC::clone(&s.port));
218        let false_guard = ir::Guard::not(true_guard.clone());
219
220        // assignments to active each FSM
221        let mut trigger_fsms_with_branch_latency = vec![(
222            builder.build_assignment(
223                true_branch_fsm.borrow().get("start"),
224                signal_on.borrow().get("out"),
225                true_guard,
226            ),
227            s.tbranch.get_latency(),
228        )];
229
230        // generate FSM and start condition for false branch if branch not empty
231        if !(matches!(&*s.fbranch, ir::StaticControl::Empty(_))) {
232            let mut sch_constructor_false = StaticSchedule::from(&mut builder);
233            let false_branch_fsm = sch_constructor_false.build_fsm(&s.fbranch);
234            trigger_fsms_with_branch_latency.push((
235                builder.build_assignment(
236                    false_branch_fsm.borrow().get("start"),
237                    signal_on.borrow().get("out"),
238                    false_guard,
239                ),
240                s.fbranch.get_latency(),
241            ));
242        }
243
244        // make sure [start] for each FSM is pulsed at most once, at the first
245        // cycle
246
247        let trigger_fsms = trigger_fsms_with_branch_latency
248            .into_iter()
249            .map(|(mut assign, latency)| {
250                assign
251                    .guard
252                    .add_interval(ir::StaticTiming::new((0, latency)));
253                assign
254            })
255            .collect_vec();
256
257        if_group.borrow_mut().assignments.extend(trigger_fsms);
258
259        // ensure this group only gets one state in the parent FSM, and only
260        // transitions out when the latency counter has completed
261        let mut enable = ir::StaticControl::Enable(ir::StaticEnable {
262            group: if_group,
263            attributes: ir::Attributes::default(),
264        });
265        enable
266            .get_mut_attributes()
267            .insert(ir::BoolAttr::OneState, 1);
268
269        Ok(Action::static_change(enable))
270    }
271
272    fn finish_static_par(
273        &mut self,
274        s: &mut calyx_ir::StaticPar,
275        comp: &mut calyx_ir::Component,
276        sigs: &calyx_ir::LibrarySignatures,
277        _comps: &[calyx_ir::Component],
278    ) -> crate::traversal::VisResult {
279        let mut builder = ir::Builder::new(comp, sigs);
280        let signal_on = builder.add_constant(1, 1);
281        let par_group = builder.add_static_group("par", s.latency);
282        par_group
283            .borrow_mut()
284            .assignments
285            .extend(s.stmts.iter().map(|thread: &ir::StaticControl| {
286                let mut sch_generator = StaticSchedule::from(&mut builder);
287                let thread_latency = thread.get_latency();
288                let thread_fsm = sch_generator.build_fsm(thread);
289                let mut trigger_thread = builder.build_assignment(
290                    thread_fsm.borrow().get("start"),
291                    signal_on.borrow().get("out"),
292                    ir::Guard::True,
293                );
294                trigger_thread
295                    .guard
296                    .add_interval(ir::StaticTiming::new((0, thread_latency)));
297                trigger_thread
298            }));
299
300        let mut enable = ir::StaticControl::Enable(ir::StaticEnable {
301            group: par_group,
302            attributes: ir::Attributes::default(),
303        });
304        enable
305            .get_mut_attributes()
306            .insert(ir::BoolAttr::OneState, 1);
307
308        Ok(Action::static_change(enable))
309    }
310
311    fn finish_static_repeat(
312        &mut self,
313        s: &mut calyx_ir::StaticRepeat,
314        comp: &mut calyx_ir::Component,
315        sigs: &calyx_ir::LibrarySignatures,
316        _comps: &[calyx_ir::Component],
317    ) -> crate::traversal::VisResult {
318        let mut builder = ir::Builder::new(comp, sigs);
319        let signal_on = builder.add_constant(1, 1);
320        let repeat_group = builder.add_static_group("repeat", s.latency);
321        let mut sch_generator = StaticSchedule::from(&mut builder);
322        // let trigger_fsm = if !one_state_exists(&s.body) {
323        let trigger_fsm = if false {
324            // If there are no states that loop in place (i.e. that have registers
325            // and adders to count latency), then we can unroll the repeat because
326            // we won't then generate a lot of these resources.
327
328            // Replace the static repeat node with a dummy node so we can create a
329            // StaticControl instance to pass into `construct_schedule`
330            let dummy_repeat = ir::StaticRepeat {
331                attributes: ir::Attributes::default(),
332                body: Box::new(ir::StaticControl::empty()),
333                num_repeats: 0,
334                latency: 0,
335            };
336
337            let repeat_node = std::mem::replace(s, dummy_repeat);
338            let sc_wrapper = ir::StaticControl::Repeat(repeat_node);
339            let fsm = sch_generator.build_fsm(&sc_wrapper);
340            let mut trigger_thread = builder.build_assignment(
341                fsm.borrow().get("start"),
342                signal_on.borrow().get("out"),
343                ir::Guard::True,
344            );
345            trigger_thread
346                .guard
347                .add_interval(ir::StaticTiming::new((0, 1)));
348            trigger_thread
349        } else {
350            // This FSM implements the schedule for the body of the repeat
351            let fsm = sch_generator.build_fsm(&s.body);
352
353            let mut trigger_thread = builder.build_assignment(
354                fsm.borrow().get("start"),
355                signal_on.borrow().get("out"),
356                ir::Guard::True,
357            );
358            // Make fsm[start] active for the entire execution of the repeat,
359            // not just the first cycle. This way, we can repeat the body the desired
360            // number of times.
361            trigger_thread
362                .guard
363                .add_interval(ir::StaticTiming::new((0, s.latency)));
364            trigger_thread
365        };
366
367        repeat_group.borrow_mut().assignments.push(trigger_fsm);
368        let mut enable = ir::StaticControl::Enable(ir::StaticEnable {
369            group: repeat_group,
370            attributes: ir::Attributes::default(),
371        });
372        enable
373            .get_mut_attributes()
374            .insert(ir::BoolAttr::OneState, 1);
375
376        Ok(Action::static_change(enable))
377    }
378}