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!(cell_ref.borrow().is_comb_cell(), "writes to more than 2 stateful cells: {first_cell_name}, {second_cell_name}, {}", cell_ref.borrow().name());
296                        self.comb_asmts.push(asmt);
297                    }
298                }
299                None => self.group_done_asmt = Some(asmt),
300            }
301        }
302    }
303    // Builds ordering for self. If there is a possible ordering of asmts that
304    // satisfy group2seq's criteria, then return the ordering in the form of
305    // Some(cell1, cell2). Otherwise return None.
306    pub fn possible_split(
307        asmts: &[ir::Assignment<T>],
308    ) -> Option<(ir::Id, ir::Id)> {
309        let stateful_writes: Vec<ir::Id> = asmts
310            .iter()
311            .analysis()
312            .cell_writes()
313            .filter_map(|cell| {
314                if cell.borrow().is_comb_cell() {
315                    None
316                } else {
317                    Some(cell.borrow().name())
318                }
319            })
320            .collect();
321
322        if stateful_writes.len() == 2 {
323            let (maybe_first, maybe_last, last) =
324                Self::look_for_assigns(asmts)?;
325            if maybe_last == last
326                // making sure maybe_first and maybe_last are the only 2 cells written to
327                && stateful_writes.contains(&maybe_first)
328                && stateful_writes.contains(&maybe_last)
329                // making sure that all reads of the first cell are from stable ports
330                && asmts.iter().all(|assign| {
331                    if_name_stable_or_done(assign, &maybe_first)
332                })
333            {
334                return Some((maybe_first, maybe_last));
335            }
336        }
337        None
338    }
339    // Searches thru asmts for an a.go = b.done, or a group[done] = c.done assignment.
340    // If we can find examples of such assignments, returns Some(b,a,c).
341    // Otherwise returns None.
342    fn look_for_assigns(
343        asmts: &[ir::Assignment<T>],
344    ) -> Option<(ir::Id, ir::Id, ir::Id)> {
345        let mut done_go: Option<(ir::Id, ir::Id)> = None;
346        let mut last: Option<ir::Id> = None;
347        for asmt in asmts {
348            let src = asmt.src.borrow();
349            let dst = asmt.dst.borrow();
350            match (&src.parent, &dst.parent) {
351                (
352                    ir::PortParent::Cell(src_cell),
353                    ir::PortParent::Cell(dst_cell),
354                ) => {
355                    // a.go = b.done case
356                    if src.attributes.has(ir::NumAttr::Done)
357                        && dst.attributes.has(ir::NumAttr::Go)
358                        && comp_or_non_comb(&src_cell.upgrade())
359                        && comp_or_non_comb(&dst_cell.upgrade())
360                    {
361                        done_go = Some((
362                            src_cell.upgrade().borrow().name(),
363                            dst_cell.upgrade().borrow().name(),
364                        ));
365                    }
366                }
367                (ir::PortParent::Cell(src_cell), ir::PortParent::Group(_)) => {
368                    // group[done] = c.done case
369                    if dst.name == "done"
370                        && src.attributes.has(ir::NumAttr::Done)
371                        && comp_or_non_comb(&src_cell.upgrade())
372                    {
373                        last = Some(src_cell.upgrade().borrow().name())
374                    }
375                }
376                // If we encounter anything else, then not of interest to us
377                _ => (),
378            }
379        }
380        let (done, go) = done_go?;
381        let last_val = last?;
382        Some((done, go, last_val))
383    }
384    //Returns whether the given assignment is a go-done assignment
385    //i.e. cell1.go = cell2.done.
386    pub fn is_go_done(asmt: &ir::Assignment<T>) -> bool {
387        let src = asmt.src.borrow();
388        let dst = asmt.dst.borrow();
389        match (&src.parent, &dst.parent) {
390            (ir::PortParent::Cell(_), ir::PortParent::Cell(_)) => {
391                src.attributes.has(ir::NumAttr::Done)
392                    && dst.attributes.has(ir::NumAttr::Go)
393            }
394            _ => false,
395        }
396    }
397
398    //Returns whether the given assignment writes to the go assignment of cell
399    //in the form cell.go = !cell.done? 1'd1.
400    pub fn is_specific_go(asmt: &ir::Assignment<T>, cell: &ir::Id) -> bool {
401        let dst = asmt.dst.borrow();
402        // checks cell.go =
403        dst.get_parent_name() == cell  && dst.attributes.has(ir::NumAttr::Go)
404        // checks !cell.done ?
405        && asmt.guard.is_not_done(cell)
406        // checks 1'd1
407        && asmt.src.borrow().is_constant(1, 1)
408    }
409}
410
411/// Template for a Generic Group (i.e., either regular or static):
412/// Includes group's assignments, done guard, and done src.
413/// Can't include the done assignment in this struct, since this struct is for *before*
414/// we've actually created the group, so we can't refer to the group yet (and we
415/// need to refer to the group to create its done port)
416/// This is intentional, since if we were to create the group, then it would
417/// no longer be generic (we would have to pick either group/static group)
418struct GroupOutline<T> {
419    assignments: Vec<ir::Assignment<T>>,
420    done_guard: ir::Guard<T>,
421    done_src: ir::RRC<ir::Port>,
422}
423
424impl GroupOutline<Nothing> {
425    /// Returns group with made using builder with prefix. The assignments are
426    /// self.assignments, plus a write to groups's done, based on done_src and done_guard.
427    fn make_group(
428        self,
429        builder: &mut ir::Builder,
430        prefix: String,
431    ) -> ir::RRC<ir::Group> {
432        let group = builder.add_group(prefix);
433        let mut group_asmts = self.assignments;
434        let done_asmt = builder.build_assignment(
435            group.borrow().get("done"),
436            self.done_src,
437            self.done_guard,
438        );
439        group_asmts.push(done_asmt);
440        group.borrow_mut().assignments.append(&mut group_asmts);
441        group
442    }
443}
444
445// impl GroupOutline<StaticTiming> {
446//     /// Returns group with made using builder with prefix. The assignments are
447//     /// self.assignments, plus a write to groups's done, based on done_src and done_guard.
448//     fn make_group_static(
449//         self,
450//         builder: &mut ir::Builder,
451//         prefix: String,
452//     ) -> ir::RRC<ir::StaticGroup> {
453//         panic!("not implemented");
454//         let group = builder.add_static_group(prefix, 0);
455//         let mut group_asmts = self.assignments;
456//         let done_asmt = builder.build_assignment(
457//             group.borrow().get(ir::NumAttr::Done),
458//             self.done_src,
459//             self.done_guard,
460//         );
461//         group_asmts.push(done_asmt);
462//         group.borrow_mut().assignments.append(&mut group_asmts);
463//         group
464//     }
465// }