calyx_opt/passes/
static_fsm_allocation.rs

1use crate::analysis::{IncompleteTransition, StaticSchedule};
2use crate::traversal::{Action, ConstructVisitor, Named, Visitor};
3use calyx_ir::{self as ir};
4use calyx_utils::CalyxResult;
5
6pub struct StaticFSMAllocation {
7    non_promoted_static_component: bool,
8}
9
10impl Named for StaticFSMAllocation {
11    fn name() -> &'static str {
12        "static-fsm-alloc"
13    }
14    fn description() -> &'static str {
15        "compiles a static schedule into an FSM construct"
16    }
17}
18
19impl ConstructVisitor for StaticFSMAllocation {
20    fn from(_ctx: &ir::Context) -> CalyxResult<Self> {
21        Ok(StaticFSMAllocation {
22            non_promoted_static_component: false,
23        })
24    }
25    fn clear_data(&mut self) {
26        self.non_promoted_static_component = false
27    }
28}
29
30impl StaticSchedule<'_, '_> {
31    /// Provided a static control node, calling this method on an empty `StaticSchedule`
32    /// `sch` will build out the `latency` and `state2assigns` fields of `sch`, in
33    /// preparation to replace the `StaticControl` node with an instance of `ir::FSM`.
34    /// Every static assignment collected into `state2assigns` will have its existing guard
35    /// "anded" with `guard`.
36    fn build_abstract_fsm_with_loop(
37        &mut self,
38        scon: &ir::StaticControl,
39        guard: ir::Guard<ir::Nothing>,
40        mut transitions_to_curr: Vec<IncompleteTransition>,
41        looped_once_guard: Option<ir::Guard<ir::Nothing>>,
42    ) -> (Vec<IncompleteTransition>, Option<ir::Guard<ir::Nothing>>) {
43        match scon {
44            ir::StaticControl::Empty(_) => {
45                (transitions_to_curr, looped_once_guard)
46            }
47            ir::StaticControl::Enable(sen) => {
48                // for all parts of the FSM that want to transition to this enable,
49                // register their transitions in self.state2trans
50                self.register_transitions(
51                    self.state,
52                    &mut transitions_to_curr,
53                    guard.clone(),
54                );
55
56                // allocate one state if requested, and have one state for every
57                // cycle otherwise
58                if sen.attributes.has(ir::BoolAttr::OneState) {
59                    let final_state_guard =
60                        self.leave_one_state_condition(guard, sen);
61
62                    let new_looped_once_guard = match self.state {
63                        0 => Some(final_state_guard.clone()),
64                        _ => looped_once_guard,
65                    };
66                    self.state += 1;
67                    (
68                        vec![IncompleteTransition::new(
69                            self.state - 1,
70                            final_state_guard,
71                        )],
72                        new_looped_once_guard,
73                    )
74                } else {
75                    sen.group.borrow().assignments.iter().for_each(|sassign| {
76                        sassign
77                            .guard
78                            .compute_live_states(sen.group.borrow().latency)
79                            .into_iter()
80                            .for_each(|offset| {
81                                // convert the static assignment to a normal one
82                                let mut assign: ir::Assignment<ir::Nothing> =
83                                    ir::Assignment::from(sassign.clone());
84                                // "and" the assignment's guard with argument guard
85                                assign.and_guard(guard.clone());
86                                // add this assignment to the list of assignments
87                                // that are supposed to be valid at this state
88                                self.state2assigns
89                                    .entry(self.state + offset)
90                                    .and_modify(|other_assigns| {
91                                        other_assigns.push(assign.clone())
92                                    })
93                                    .or_insert(vec![assign]);
94                            })
95                    });
96                    self.state += sen.group.borrow().latency;
97                    // Don't know where to transition next; let the parent that called
98                    // `build_abstract_fsm_with_loop` deal with registering the transition
99                    // from the state(s) we just built.
100                    (
101                        vec![IncompleteTransition::new(
102                            self.state - 1,
103                            ir::Guard::True,
104                        )],
105                        looped_once_guard,
106                    )
107                }
108            }
109            ir::StaticControl::Seq(sseq) => sseq.stmts.iter().fold(
110                (transitions_to_curr, looped_once_guard),
111                |(transitions_to_this_stmt, looped_once_guard_this_stmt),
112                 stmt| {
113                    self.build_abstract_fsm_with_loop(
114                        stmt,
115                        guard.clone(),
116                        transitions_to_this_stmt,
117                        looped_once_guard_this_stmt,
118                    )
119                },
120            ),
121
122            ir::StaticControl::If(_) => {
123                unreachable!(
124                    "`construct_schedule` encountered a `static_if` node. \
125              Should have been compiled into a static group."
126                )
127            }
128            ir::StaticControl::Repeat(_) => {
129                unreachable!(
130                    "`construct_schedule` encountered a `static_repeat` node. \
131              Should have been compiled into a static group."
132                )
133            }
134            ir::StaticControl::Par(_) => {
135                unreachable!(
136                    "`construct_schedule` encountered a `static_par` node. \
137              Should have been compiled into a static group."
138                )
139            }
140            ir::StaticControl::Invoke(_) => {
141                unreachable!(
142                    "`construct_schedule` encountered a `static_invoke` node. \
143              Should have been compiled away."
144                )
145            }
146        }
147    }
148
149    /// Given a filled-out static schedule, construct an FSM based on the state mappings
150    /// in `state2assigns`.
151    fn realize_fsm(
152        &mut self,
153        control: &ir::StaticControl,
154        non_promoted_static_component: bool,
155    ) -> ir::RRC<ir::FSM> {
156        let true_guard = ir::Guard::True;
157        let signal_on = self.builder.add_constant(1, 1);
158
159        // Declare the FSM
160        let fsm = self.builder.add_fsm("fsm");
161
162        let (mut remaining_assignments, additional_looped_once_guard) = self
163            .build_abstract_fsm_with_loop(
164                control,
165                ir::Guard::True,
166                vec![],
167                None,
168            );
169
170        // add loopback transitions to first state
171        self.register_transitions(
172            0,
173            &mut remaining_assignments,
174            ir::Guard::True,
175        );
176
177        let (mut assignments, transitions, state2wires) =
178            self.build_fsm_pieces(ir::RRC::clone(&fsm));
179
180        if non_promoted_static_component {
181            // If the component is static by design, there will be exactly one
182            // FSM allocated to it. We will get rid of the FSMEnable node from the
183            // control in this case, so we need to manually add fsm[start] = comp[go]
184            // because wire-inliner will not get to it.
185
186            // (We get rid of the FSMEnable node because the FSM will not have a
187            // DONE state, and hence no way to terminate the control. )
188            let assign_fsm_start = self.builder.build_assignment(
189                fsm.borrow().get("start"),
190                self.builder
191                    .component
192                    .signature
193                    .borrow()
194                    .find_unique_with_attr(ir::NumAttr::Go)
195                    .unwrap()
196                    .unwrap(),
197                true_guard,
198            );
199            self.builder
200                .add_continuous_assignments(vec![assign_fsm_start]);
201        } else {
202            // In this case, the component is either a promoted static component
203            // or the control is a static island that needs to handshake with its
204            // surrounding dynamic context. In either event, we want to assign
205            // fsm[done] to maintain the dynamic interface. We'll do this in state 0:
206
207            // register to store whether the FSM has been run exactly one time when
208            // we return to state 0
209            let looped_once: ir::RRC<ir::Cell> =
210                self.builder.add_primitive("looped_once", "std_reg", &[1]);
211
212            looped_once
213                .borrow_mut()
214                .add_attribute(ir::BoolAttr::FSMControl, 1);
215
216            let (assign_looped_once, assign_looped_once_we, fsm_done) = (
217                self.builder.build_assignment(
218                    looped_once.borrow().get("in"),
219                    signal_on.borrow().get("out"),
220                    match additional_looped_once_guard {
221                        None => ir::guard!(fsm["start"]),
222                        Some(g) => ir::guard!(fsm["start"]).and(g),
223                    },
224                ),
225                self.builder.build_assignment(
226                    looped_once.borrow().get("write_en"),
227                    signal_on.borrow().get("out"),
228                    ir::Guard::True,
229                ),
230                self.builder.build_assignment(
231                    fsm.borrow().get("done"),
232                    looped_once.borrow().get("out"),
233                    ir::Guard::True,
234                ),
235            );
236
237            assignments.first_mut().unwrap().extend(vec![
238                assign_looped_once,
239                assign_looped_once_we,
240                fsm_done,
241            ]);
242        }
243
244        self.builder.add_continuous_assignments(
245            self.state2assigns
246                .drain()
247                .flat_map(|(state, mut assigns)| {
248                    assigns.iter_mut().for_each(|assign| {
249                        assign.and_guard(ir::Guard::port(
250                            state2wires
251                                .get(state as usize)
252                                .unwrap()
253                                .borrow()
254                                .get("out"),
255                        ));
256                    });
257                    assigns
258                })
259                .collect(),
260        );
261
262        // Instantiate the FSM with the assignments and transitions we built
263        fsm.borrow_mut().extend_fsm(assignments, transitions);
264        fsm
265    }
266}
267
268impl Visitor for StaticFSMAllocation {
269    fn start_static_control(
270        &mut self,
271        s: &mut calyx_ir::StaticControl,
272        comp: &mut calyx_ir::Component,
273        sigs: &calyx_ir::LibrarySignatures,
274        _comps: &[calyx_ir::Component],
275    ) -> crate::traversal::VisResult {
276        self.non_promoted_static_component = comp.is_static()
277            && !(comp
278                .attributes
279                .has(ir::Attribute::Bool(ir::BoolAttr::Promoted)));
280        let mut builder = ir::Builder::new(comp, sigs);
281
282        let mut ssch = StaticSchedule::from(&mut builder);
283
284        Ok(Action::change(ir::Control::fsm_enable(
285            ssch.realize_fsm(s, self.non_promoted_static_component),
286        )))
287    }
288    fn finish(
289        &mut self,
290        _comp: &mut ir::Component,
291        _sigs: &ir::LibrarySignatures,
292        _comps: &[ir::Component],
293    ) -> crate::traversal::VisResult {
294        // If the component is static, get rid of all control components;
295        // all assignments should already exist in the `wires` section
296        if self.non_promoted_static_component {
297            Ok(Action::Change(Box::new(ir::Control::empty())))
298        } else {
299            Ok(Action::Continue)
300        }
301    }
302}