calyx_opt/passes/
group_to_seq.rs

1use crate::analysis::{AssignmentAnalysis, ReadWriteSet};
2use crate::traversal::{Action, Named, VisResult, Visitor};
3use calyx_ir as ir;
4use ir::Nothing;
5use std::collections::BTreeMap;
6
7#[derive(Default)]
8/// Transforms a group into a seq of 2 smaller groups, if possible.
9/// Currently, in order for a group to be transformed must
10/// 1) Group must write to exactly 2 cells -- let's call them cell1 and cell2
11/// 2) cell1 and cell2 must be either non-combinational primitives or components
12/// 3) Must have group[done] = cell2.done and cell2.go = cell1.done;
13/// 4) All reads of cell1 must be a stable port or cell1.done.
14pub struct GroupToSeq {
15    ///Maps names of group to the sequences that will replace them
16    group_seq_map: BTreeMap<ir::Id, ir::Control>,
17}
18
19impl Named for GroupToSeq {
20    fn name() -> &'static str {
21        "group2seq"
22    }
23
24    fn description() -> &'static str {
25        "split groups under correct conditions"
26    }
27}
28
29impl Visitor for GroupToSeq {
30    fn start(
31        &mut self,
32        comp: &mut ir::Component,
33        sigs: &ir::LibrarySignatures,
34        _comps: &[ir::Component],
35    ) -> VisResult {
36        let groups: Vec<ir::RRC<ir::Group>> =
37            comp.get_groups_mut().drain().collect();
38        let mut builder = ir::Builder::new(comp, sigs);
39        for g in groups.iter() {
40            let mut g_ref = g.borrow_mut();
41            let group_name = g_ref.name();
42            let split_analysis: SplitAnalysis<Nothing> =
43                SplitAnalysis::default();
44            if let Some((outline1, outline2)) = split_analysis.get_split(
45                &mut g_ref.assignments,
46                group_name,
47                &mut builder,
48            ) {
49                let g1 = outline1.make_group(
50                    &mut builder,
51                    format!("beg_spl_{}", g_ref.name().id),
52                );
53                let g2 = outline2.make_group(
54                    &mut builder,
55                    format!("end_spl_{}", g_ref.name().id),
56                );
57                let seq = ir::Control::seq(vec![
58                    ir::Control::enable(g1),
59                    ir::Control::enable(g2),
60                ]);
61                self.group_seq_map.insert(group_name, seq);
62            }
63        }
64
65        // Add back the groups we drained at the beginning of this method, but
66        // filter out the empty groups that were split into smaller groups
67        comp.get_groups_mut().append(
68            groups
69                .into_iter()
70                .filter(|group| !group.borrow().assignments.is_empty()),
71        );
72
73        // // do the same thing with static groups
74        // let static_groups: Vec<ir::RRC<ir::StaticGroup>> =
75        //     comp.get_static_groups_mut().drain().collect();
76        // let mut builder = ir::Builder::new(comp, sigs);
77        // for sg in static_groups.iter() {
78        //     let split_analysis: SplitAnalysis<StaticTiming> =
79        //         SplitAnalysis::default();
80        //     if let Some((outline1, outline2)) = split_analysis.get_split(
81        //         &mut sg.borrow_mut().assignments,
82        //         sg.borrow().name(),
83        //         &mut builder,
84        //     ) {
85        //         let g1 = outline1.make_group_static(
86        //             &mut builder,
87        //             format!("beg_spl_{}", sg.borrow().name().id),
88        //         );
89        //         let g2 = outline2.make_group_static(
90        //             &mut builder,
91        //             format!("end_spl{}", sg.borrow().name().id),
92        //         );
93        //         let seq = ir::Control::seq(vec![
94        //             ir::Control::static_enable(g1),
95        //             ir::Control::static_enable(g2),
96        //         ]);
97        //         self.group_seq_map.insert(sg.borrow().name(), seq);
98        //     }
99        // }
100
101        // // Add back the groups we drained at the beginning of this method, but
102        // // filter out the empty groups that were split into smaller groups
103        // comp.get_static_groups_mut()
104        //     .append(static_groups.into_iter().filter(|static_group| {
105        //         !static_group.borrow().assignments.is_empty()
106        //     }));
107
108        Ok(Action::Continue)
109    }
110
111    fn enable(
112        &mut self,
113        s: &mut ir::Enable,
114        _comp: &mut ir::Component,
115        _sigs: &ir::LibrarySignatures,
116        _comps: &[ir::Component],
117    ) -> VisResult {
118        let group_name = s.group.borrow().name();
119        match self.group_seq_map.get(&group_name) {
120            None => Ok(Action::Continue),
121            Some(seq) => Ok(Action::Change(Box::new(ir::Cloner::control(seq)))),
122        }
123    }
124}
125
126// For all port reads from name in assignment, returns whether all ports are either stable
127// or done.
128fn if_name_stable_or_done<T>(
129    assign: &ir::Assignment<T>,
130    name: &ir::Id,
131) -> bool {
132    let reads = ReadWriteSet::port_reads(assign);
133    reads
134        .filter(|port_ref| port_ref.borrow().get_parent_name() == name)
135        .all(|port_ref| {
136            let atts = &port_ref.borrow().attributes;
137            atts.has(ir::BoolAttr::Stable) || atts.has(ir::NumAttr::Done)
138        })
139}
140
141// Returns true if the cell is a component or a non-combinational primitive
142fn comp_or_non_comb(cell: &ir::RRC<ir::Cell>) -> bool {
143    match &cell.borrow().prototype {
144        ir::CellType::Primitive { is_comb, .. } => !*is_comb,
145        ir::CellType::Component { .. } => true,
146        _ => false,
147    }
148}
149
150//If asmt is a write to a cell named name returns Some(name).
151//If asmt is a write to a group port, returns None.
152fn writes_to_cell<T>(asmt: &ir::Assignment<T>) -> Option<ir::RRC<ir::Cell>> {
153    std::iter::once(asmt).analysis().cell_writes().next()
154}
155
156///Primarily used to help determine the order cells are executed within
157///the group, and if possible, to transform a group into a seq of two smaller groups
158struct SplitAnalysis<T>
159where
160    T: Clone,
161{
162    /// Holds the go-done assignment, i.e. a.go = b.done
163    go_done_asmt: Option<ir::Assignment<T>>,
164
165    /// Holds the first "go" assignment, *if* it is in the form a.go = !a.done ? 1'd1
166    first_go_asmt: Option<ir::Assignment<T>>,
167
168    /// Holds the group[done] = done assignment;
169    group_done_asmt: Option<ir::Assignment<T>>,
170
171    /// Assignments that write to first cell, unless the assignment is already accounted by a different field
172    fst_asmts: Vec<ir::Assignment<T>>,
173
174    /// Assignments that write to second cell, unless the assignment is already accounted by a different field
175    snd_asmts: Vec<ir::Assignment<T>>,
176
177    /// Writes to combinational components
178    comb_asmts: Vec<ir::Assignment<T>>,
179}
180
181impl<T> Default for SplitAnalysis<T>
182where
183    T: Clone,
184{
185    fn default() -> Self {
186        SplitAnalysis {
187            go_done_asmt: None,
188            first_go_asmt: None,
189            group_done_asmt: None,
190            fst_asmts: Vec::default(),
191            snd_asmts: Vec::default(),
192            comb_asmts: Vec::default(),
193        }
194    }
195}
196
197impl<T> SplitAnalysis<T>
198where
199    T: Clone,
200{
201    /// Based on assigns, returns Some(seq), where seq = [group1,group2], which
202    /// the groups that can be made by splitting assigns. If it is not possible to split
203    /// assigns into two groups, then just regurn None.
204    /// Criteria for being able to split assigns into two groups (this criteria
205    /// is already specified in group2seq's description as well):
206    /// 1) Group must write to exactly 2 cells -- let's call them cell1 and cell2
207    /// 2) cell1 and cell2 must be either non-combinational primitives or components
208    /// 3) Must have group[done] = cell2.done and cell2.go = cell1.done;
209    /// 4) All reads of cell1 must be a stable port or cell1.done.
210    pub fn get_split(
211        mut self,
212        assigns: &mut Vec<ir::Assignment<T>>,
213        group_name: ir::Id,
214        builder: &mut ir::Builder,
215    ) -> Option<(GroupOutline<T>, GroupOutline<T>)> {
216        let signal_on = builder.add_constant(1, 1);
217        // Builds ordering. If it cannot build a valid linear ordering of length 2,
218        // then returns None, and we stop.
219        let (first, second) = SplitAnalysis::possible_split(assigns)?;
220
221        // Sets the first_go_asmt, fst_asmts, snd_asmts group_done_asmt, go_done_asmt
222        // fields for split_analysis
223        self.organize_assignments(assigns, &first, &second);
224
225        // If there is assignment in the form first.go = !first.done ? 1'd1,
226        // turn this into first.go = 1'd1.
227        if let Some(go_asmt) = self.first_go_asmt {
228            let new_go_asmt = builder.build_assignment(
229                go_asmt.dst,
230                signal_on.borrow().get("out"),
231                ir::Guard::True,
232            );
233            self.fst_asmts.push(new_go_asmt);
234        }
235        let comb_assigns_clones = self.comb_asmts.clone();
236        // writes to comb components should be included in the first group
237        self.fst_asmts.extend(comb_assigns_clones);
238
239        let go_done = self.go_done_asmt.unwrap_or_else(|| {
240            unreachable!("couldn't find a go-done assignment in {}", group_name)
241        });
242
243        // Pushing second.go = 1'd1 onto snd_asmts
244        let cell_go = builder.build_assignment(
245            go_done.dst,
246            signal_on.borrow().get("out"),
247            ir::Guard::True,
248        );
249        self.snd_asmts.push(cell_go);
250        // writes to comb assigns should also be in the second group
251        self.snd_asmts.extend(self.comb_asmts);
252
253        let group_done = self.group_done_asmt.unwrap_or_else(|| {
254            unreachable!(
255                "Couldn't find a group[done] = _.done assignment in {}",
256                group_name
257            )
258        });
259
260        let g1_outline: GroupOutline<T> = GroupOutline {
261            assignments: self.fst_asmts,
262            done_guard: ir::Guard::True,
263            done_src: go_done.src,
264        };
265        let g2_outline: GroupOutline<T> = GroupOutline {
266            assignments: self.snd_asmts,
267            done_guard: *group_done.guard,
268            done_src: group_done.src,
269        };
270        Some((g1_outline, g2_outline))
271    }
272
273    // Goes through assignments, and properly fills in the fields go_done_asmt,
274    // first_go_asmt, fst_asmts, snd_asmts, and group_done_asmt.
275    fn organize_assignments(
276        &mut self,
277        assigns: &mut Vec<ir::Assignment<T>>,
278        first_cell_name: &ir::Id,
279        second_cell_name: &ir::Id,
280    ) {
281        for asmt in assigns.drain(..) {
282            match writes_to_cell(&asmt) {
283                Some(cell_ref) => {
284                    let cell_name = cell_ref.borrow().name();
285                    if Self::is_go_done(&asmt) {
286                        self.go_done_asmt = Some(asmt);
287                    } else if Self::is_specific_go(&asmt, first_cell_name) {
288                        self.first_go_asmt = Some(asmt);
289                    } else if cell_name == first_cell_name {
290                        self.fst_asmts.push(asmt);
291                    } else if cell_name == second_cell_name {
292                        self.snd_asmts.push(asmt);
293                    } else {
294                        // assert that we're writing to a combinational component
295                        assert!(
296                            cell_ref.borrow().is_comb_cell(),
297                            "writes to more than 2 stateful cells: {first_cell_name}, {second_cell_name}, {}",
298                            cell_ref.borrow().name()
299                        );
300                        self.comb_asmts.push(asmt);
301                    }
302                }
303                None => self.group_done_asmt = Some(asmt),
304            }
305        }
306    }
307    // Builds ordering for self. If there is a possible ordering of asmts that
308    // satisfy group2seq's criteria, then return the ordering in the form of
309    // Some(cell1, cell2). Otherwise return None.
310    pub fn possible_split(
311        asmts: &[ir::Assignment<T>],
312    ) -> Option<(ir::Id, ir::Id)> {
313        let stateful_writes: Vec<ir::Id> = asmts
314            .iter()
315            .analysis()
316            .cell_writes()
317            .filter_map(|cell| {
318                if cell.borrow().is_comb_cell() {
319                    None
320                } else {
321                    Some(cell.borrow().name())
322                }
323            })
324            .collect();
325
326        if stateful_writes.len() == 2 {
327            let (maybe_first, maybe_last, last) =
328                Self::look_for_assigns(asmts)?;
329            if maybe_last == last
330                // making sure maybe_first and maybe_last are the only 2 cells written to
331                && stateful_writes.contains(&maybe_first)
332                && stateful_writes.contains(&maybe_last)
333                // making sure that all reads of the first cell are from stable ports
334                && asmts.iter().all(|assign| {
335                    if_name_stable_or_done(assign, &maybe_first)
336                })
337            {
338                return Some((maybe_first, maybe_last));
339            }
340        }
341        None
342    }
343    // Searches thru asmts for an a.go = b.done, or a group[done] = c.done assignment.
344    // If we can find examples of such assignments, returns Some(b,a,c).
345    // Otherwise returns None.
346    fn look_for_assigns(
347        asmts: &[ir::Assignment<T>],
348    ) -> Option<(ir::Id, ir::Id, ir::Id)> {
349        let mut done_go: Option<(ir::Id, ir::Id)> = None;
350        let mut last: Option<ir::Id> = None;
351        for asmt in asmts {
352            let src = asmt.src.borrow();
353            let dst = asmt.dst.borrow();
354            match (&src.parent, &dst.parent) {
355                (
356                    ir::PortParent::Cell(src_cell),
357                    ir::PortParent::Cell(dst_cell),
358                ) => {
359                    // a.go = b.done case
360                    if src.attributes.has(ir::NumAttr::Done)
361                        && dst.attributes.has(ir::NumAttr::Go)
362                        && comp_or_non_comb(&src_cell.upgrade())
363                        && comp_or_non_comb(&dst_cell.upgrade())
364                    {
365                        done_go = Some((
366                            src_cell.upgrade().borrow().name(),
367                            dst_cell.upgrade().borrow().name(),
368                        ));
369                    }
370                }
371                (ir::PortParent::Cell(src_cell), ir::PortParent::Group(_)) => {
372                    // group[done] = c.done case
373                    if dst.name == "done"
374                        && src.attributes.has(ir::NumAttr::Done)
375                        && comp_or_non_comb(&src_cell.upgrade())
376                    {
377                        last = Some(src_cell.upgrade().borrow().name())
378                    }
379                }
380                // If we encounter anything else, then not of interest to us
381                _ => (),
382            }
383        }
384        let (done, go) = done_go?;
385        let last_val = last?;
386        Some((done, go, last_val))
387    }
388    //Returns whether the given assignment is a go-done assignment
389    //i.e. cell1.go = cell2.done.
390    pub fn is_go_done(asmt: &ir::Assignment<T>) -> bool {
391        let src = asmt.src.borrow();
392        let dst = asmt.dst.borrow();
393        match (&src.parent, &dst.parent) {
394            (ir::PortParent::Cell(_), ir::PortParent::Cell(_)) => {
395                src.attributes.has(ir::NumAttr::Done)
396                    && dst.attributes.has(ir::NumAttr::Go)
397            }
398            _ => false,
399        }
400    }
401
402    //Returns whether the given assignment writes to the go assignment of cell
403    //in the form cell.go = !cell.done? 1'd1.
404    pub fn is_specific_go(asmt: &ir::Assignment<T>, cell: &ir::Id) -> bool {
405        let dst = asmt.dst.borrow();
406        // checks cell.go =
407        dst.get_parent_name() == cell  && dst.attributes.has(ir::NumAttr::Go)
408        // checks !cell.done ?
409        && asmt.guard.is_not_done(cell)
410        // checks 1'd1
411        && asmt.src.borrow().is_constant(1, 1)
412    }
413}
414
415/// Template for a Generic Group (i.e., either regular or static):
416/// Includes group's assignments, done guard, and done src.
417/// Can't include the done assignment in this struct, since this struct is for *before*
418/// we've actually created the group, so we can't refer to the group yet (and we
419/// need to refer to the group to create its done port)
420/// This is intentional, since if we were to create the group, then it would
421/// no longer be generic (we would have to pick either group/static group)
422struct GroupOutline<T> {
423    assignments: Vec<ir::Assignment<T>>,
424    done_guard: ir::Guard<T>,
425    done_src: ir::RRC<ir::Port>,
426}
427
428impl GroupOutline<Nothing> {
429    /// Returns group with made using builder with prefix. The assignments are
430    /// self.assignments, plus a write to groups's done, based on done_src and done_guard.
431    fn make_group(
432        self,
433        builder: &mut ir::Builder,
434        prefix: String,
435    ) -> ir::RRC<ir::Group> {
436        let group = builder.add_group(prefix);
437        let mut group_asmts = self.assignments;
438        let done_asmt = builder.build_assignment(
439            group.borrow().get("done"),
440            self.done_src,
441            self.done_guard,
442        );
443        group_asmts.push(done_asmt);
444        group.borrow_mut().assignments.append(&mut group_asmts);
445        group
446    }
447}
448
449// impl GroupOutline<StaticTiming> {
450//     /// Returns group with made using builder with prefix. The assignments are
451//     /// self.assignments, plus a write to groups's done, based on done_src and done_guard.
452//     fn make_group_static(
453//         self,
454//         builder: &mut ir::Builder,
455//         prefix: String,
456//     ) -> ir::RRC<ir::StaticGroup> {
457//         panic!("not implemented");
458//         let group = builder.add_static_group(prefix, 0);
459//         let mut group_asmts = self.assignments;
460//         let done_asmt = builder.build_assignment(
461//             group.borrow().get(ir::NumAttr::Done),
462//             self.done_src,
463//             self.done_guard,
464//         );
465//         group_asmts.push(done_asmt);
466//         group.borrow_mut().assignments.append(&mut group_asmts);
467//         group
468//     }
469// }