calyx_opt/passes/
static_inliner.rs

1use crate::traversal::{Action, Named, VisResult, Visitor};
2use calyx_ir as ir;
3use calyx_ir::structure;
4use calyx_ir::LibrarySignatures;
5use ir::build_assignments;
6use std::rc::Rc;
7
8#[derive(Default)]
9pub struct StaticInliner;
10
11impl Named for StaticInliner {
12    fn name() -> &'static str {
13        "static-inline"
14    }
15
16    fn description() -> &'static str {
17        "Compiles Static Control into a single Static Enable"
18    }
19}
20
21impl StaticInliner {
22    // updates single assignment in the same way `update_assignments_timing` does
23    // adds offset to each timing guard in `assigns`
24    // e.g., %[2,3] with offset = 2 -> %[4,5]
25    // all guards also must update so that guard -> guard & %[offset, offset+latency] since that
26    // is when the group will be active in the control, i.e., dst = guard ? src
27    // becomes dst = guard & %[offset, offset+latency] ? src
28    fn update_assignment_timing(
29        assign: &mut ir::Assignment<ir::StaticTiming>,
30        offset: u64,
31        latency: u64,
32    ) {
33        // adding the offset to each timing interval
34        assign.for_each_interval(|timing_interval| {
35            let (beg, end) = timing_interval.get_interval();
36            Some(ir::Guard::Info(ir::StaticTiming::new((
37                beg + offset,
38                end + offset,
39            ))))
40        });
41        // adding the interval %[offset, offset + latency]
42        assign
43            .guard
44            .add_interval(ir::StaticTiming::new((offset, offset + latency)));
45    }
46
47    // calls update_assignment_timing on each assignment in assigns, which does the following:
48    // adds offset to each timing guard in `assigns`
49    // e.g., %[2,3] with offset = 2 -> %[4,5]
50    // all guards also must update so that guard -> guard & %[offset, offset+latency] since that
51    // is when the group will be active in the control, i.e., dst = guard ? src
52    // becomes dst =  guard & %[offset, offset+latency] ? src
53    // total_latency is the latency of the entire control block being inlined.
54    fn update_assignments_timing(
55        assigns: &mut Vec<ir::Assignment<ir::StaticTiming>>,
56        offset: u64,
57        latency: u64,
58        total_latency: u64,
59    ) {
60        if offset == 0 && latency == total_latency {
61            // In this special case, we do nothing, since the timing guards
62            // would be redundant.
63            return;
64        }
65        for assign in assigns {
66            Self::update_assignment_timing(assign, offset, latency);
67        }
68    }
69
70    // Makes assignments such that if branches can start executing on the first
71    // possible cycle.
72    // essentially, on the first cycle, we write port's value into a `cond` = a register.
73    // this is because the tru/false branch might alter port's value when it executes
74    // cond_wire reads from port on the first cycle, and then cond for the other cycles.
75    // this means that all of the tru branch assigns can get the cond_wire ? in front of them,
76    // and all false branch assigns can get !cond_wire ? in front of them
77    // makes the following assignments:
78    // read more here: https://github.com/calyxir/calyx/issues/1344 (specifically
79    // the section "Conditionl")
80    fn make_cond_assigns(
81        cond: ir::RRC<ir::Cell>,
82        cond_wire: ir::RRC<ir::Cell>,
83        port: ir::RRC<ir::Port>,
84        latency: u64,
85        builder: &mut ir::Builder,
86    ) -> Vec<ir::Assignment<ir::StaticTiming>> {
87        structure!( builder;
88            let signal_on = constant(1,1);
89        );
90        let mut cond_assigns = vec![];
91        let cycle_0_guard = ir::Guard::Info(ir::StaticTiming::new((0, 1)));
92        // = %[1:latency] ?
93        let other_cycles_guard =
94            ir::Guard::Info(ir::StaticTiming::new((1, latency)));
95        // cond.in = port
96        let cond_gets_port = builder.build_assignment(
97            cond.borrow().get("in"),
98            Rc::clone(&port),
99            ir::Guard::True,
100        );
101        // cond_wire.in = %0 ? port
102        let cond_wire_gets_port = builder.build_assignment(
103            cond_wire.borrow().get("in"),
104            port,
105            cycle_0_guard.clone(),
106        );
107        cond_assigns.push(cond_gets_port);
108        cond_assigns.push(cond_wire_gets_port);
109        let asgns = build_assignments!(builder;
110            // cond.write_en = %0 ? 1'd1 (since we also have cond.in = %0 ? port)
111            // cond_wire.in = %[1:latency] ? cond.out (since we also have cond_wire.in = %0 ? port)
112            cond["write_en"] = cycle_0_guard ? signal_on["out"];
113            cond_wire["in"] = other_cycles_guard ? cond["out"];
114        );
115        cond_assigns.extend(asgns.to_vec());
116        cond_assigns
117    }
118
119    // inlines the static control `sc` and returns an equivalent single static group
120    fn inline_static_control(
121        sc: &ir::StaticControl,
122        builder: &mut ir::Builder,
123    ) -> ir::RRC<ir::StaticGroup> {
124        match sc {
125            ir::StaticControl::Enable(ir::StaticEnable { group, .. }) => {
126                Rc::clone(group)
127            }
128            ir::StaticControl::Seq(ir::StaticSeq {
129                stmts,
130                latency,
131                attributes,
132            }) => {
133                let seq_group =
134                    builder.add_static_group("static_seq", *latency);
135                let mut seq_group_assigns: Vec<
136                    ir::Assignment<ir::StaticTiming>,
137                > = vec![];
138                let mut cur_offset = 0;
139                for stmt in stmts {
140                    let stmt_latency = stmt.get_latency();
141                    // first recursively call each stmt in seq, and turn each stmt
142                    // into static group g.
143                    let g = StaticInliner::inline_static_control(stmt, builder);
144                    assert!(
145                        g.borrow().get_latency() == stmt_latency,
146                        "static group latency doesn't match static stmt latency"
147                    );
148                    // get the assignments from g
149                    // currently we clone, since we might need these assignments elsewhere
150                    // We could probably do some sort of analysis to see when we need to
151                    // clone vs. can drain
152                    let mut g_assigns: Vec<ir::Assignment<ir::StaticTiming>> =
153                        g.borrow_mut().assignments.clone();
154                    // add cur_offset to each static guard in g_assigns
155                    // and add %[offset, offset + latency] to each assignment in
156                    // g_assigns
157                    StaticInliner::update_assignments_timing(
158                        &mut g_assigns,
159                        cur_offset,
160                        stmt_latency,
161                        *latency,
162                    );
163                    // add g_assigns to seq_group_assigns
164                    seq_group_assigns.extend(g_assigns.into_iter());
165                    // updates cur_offset so that next stmt gets its static timing
166                    // offset appropriately
167                    cur_offset += stmt_latency;
168                }
169                assert!(
170                    *latency == cur_offset,
171                    "static group latency doesn't match static seq latency"
172                );
173                seq_group.borrow_mut().assignments = seq_group_assigns;
174                seq_group.borrow_mut().attributes = attributes.clone();
175                seq_group
176            }
177            ir::StaticControl::Par(ir::StaticPar {
178                stmts,
179                latency,
180                attributes,
181            }) => {
182                let par_group =
183                    builder.add_static_group("static_par", *latency);
184                let mut par_group_assigns: Vec<
185                    ir::Assignment<ir::StaticTiming>,
186                > = vec![];
187                for stmt in stmts {
188                    let stmt_latency = stmt.get_latency();
189                    // recursively turn each stmt in the par block into a group g
190                    let g = StaticInliner::inline_static_control(stmt, builder);
191                    assert!(
192                        g.borrow().get_latency() == stmt_latency,
193                        "static group latency doesn't match static stmt latency"
194                    );
195                    // get the assignments from g
196                    // see note on the StaticControl::Seq(..) case abt why we need to clone
197                    let mut g_assigns: Vec<ir::Assignment<ir::StaticTiming>> =
198                        g.borrow_mut().assignments.clone();
199                    // offset = 0 (all start at beginning of par),
200                    // but still should add %[0:stmt_latency] to beginning of group
201                    StaticInliner::update_assignments_timing(
202                        &mut g_assigns,
203                        0,
204                        stmt_latency,
205                        *latency,
206                    );
207                    // add g_assigns to par_group_assigns
208                    par_group_assigns.extend(g_assigns.into_iter());
209                }
210                par_group.borrow_mut().assignments = par_group_assigns;
211                par_group.borrow_mut().attributes = attributes.clone();
212                par_group
213            }
214            ir::StaticControl::If(ir::StaticIf {
215                port,
216                tbranch,
217                fbranch,
218                latency,
219                attributes,
220            }) => {
221                // Making sure max of the two branches latency is the latency
222                // of the if statement
223                let tbranch_latency = tbranch.get_latency();
224                let fbranch_latency = fbranch.get_latency();
225                let max_latency =
226                    std::cmp::max(tbranch_latency, fbranch_latency);
227                assert_eq!(max_latency, *latency, "if group latency and max of the if branch latencies do not match");
228
229                // Inline assignments in tbranch and fbranch, and get resulting
230                // tgroup_assigns and fgroup_assigns
231                let tgroup =
232                    StaticInliner::inline_static_control(tbranch, builder);
233                let mut tgroup_assigns: Vec<ir::Assignment<ir::StaticTiming>> =
234                    tgroup.borrow_mut().assignments.clone();
235                assert_eq!(
236                    tbranch_latency,
237                    tgroup.borrow().get_latency(),
238                    "tru branch and tru branch group latency do not match"
239                );
240                // turn fgroup (if it exists) into group and put assigns into fgroup_assigns
241                let mut fgroup_assigns: Vec<ir::Assignment<ir::StaticTiming>> =
242                    match **fbranch {
243                        ir::StaticControl::Empty(_) => vec![],
244                        _ => {
245                            let fgroup = StaticInliner::inline_static_control(
246                                fbranch, builder,
247                            );
248                            assert_eq!(fbranch_latency, fgroup.borrow().get_latency(), "false branch and false branch group latency do not match");
249                            let fgroup_assigns: Vec<
250                                ir::Assignment<ir::StaticTiming>,
251                            > = fgroup.borrow_mut().assignments.clone();
252                            fgroup_assigns
253                        }
254                    };
255
256                // if_group = the eventual group we inline all the assignments
257                // into.
258                let if_group = builder.add_static_group("static_if", *latency);
259                let mut if_group_assigns: Vec<
260                    ir::Assignment<ir::StaticTiming>,
261                > = vec![];
262                if *latency == 1 {
263                    // Special case: if latency = 1, we don't need a register
264                    // to hold the value of the cond port.
265                    let cond_port_guard = ir::Guard::Port(Rc::clone(port));
266                    let not_cond_port_guard =
267                        ir::Guard::Not(Box::new(cond_port_guard.clone()));
268                    tgroup_assigns.iter_mut().for_each(|assign| {
269                        // adds the cond_port ? guard
270                        assign
271                            .guard
272                            .update(|guard| guard.and(cond_port_guard.clone()))
273                    });
274                    fgroup_assigns.iter_mut().for_each(|assign| {
275                        // adds the !cond_port ? guard
276                        assign.guard.update(|guard| {
277                            guard.and(not_cond_port_guard.clone())
278                        })
279                    });
280                } else {
281                    // If latency != 1, we do need a register to hold the
282                    // value of the cond port.
283                    structure!( builder;
284                        let cond = prim std_reg(port.borrow().width);
285                        let cond_wire = prim std_wire(port.borrow().width);
286                    );
287                    // build_cond_assigns makes assigns such that
288                    // cond_wire.in can guard all of the tru branch assigns,
289                    // and !cond_wire.in can guard all fo the false branch assigns
290                    let cond_assigns = StaticInliner::make_cond_assigns(
291                        Rc::clone(&cond),
292                        Rc::clone(&cond_wire),
293                        Rc::clone(port),
294                        *latency,
295                        builder,
296                    );
297                    if_group_assigns.extend(cond_assigns.to_vec());
298
299                    // need to do two things:
300                    // add cond_wire.out ? in front of each tgroup assignment
301                    // (and ! cond_wire.out for fgroup assignemnts)
302                    // add %[0:tbranch_latency] in front of each tgroup assignment
303                    // (and %[0: fbranch_latency]) in front of each fgroup assignment
304                    let cond_wire_guard =
305                        ir::Guard::Port(cond_wire.borrow().get("out"));
306                    let not_cond_wire_guard =
307                        ir::Guard::Not(Box::new(cond_wire_guard.clone()));
308                    tgroup_assigns.iter_mut().for_each(|assign| {
309                        // adds the %[0:tbranch_latency] ? guard
310                        Self::update_assignment_timing(
311                            assign,
312                            0,
313                            tbranch_latency,
314                        );
315                        // adds the cond_wire ? guard
316                        assign
317                            .guard
318                            .update(|guard| guard.and(cond_wire_guard.clone()))
319                    });
320                    fgroup_assigns.iter_mut().for_each(|assign| {
321                        // adds the %[0:fbranch_latency] ? guard
322                        Self::update_assignment_timing(
323                            assign,
324                            0,
325                            fbranch_latency,
326                        );
327                        // adds the !cond_wire ? guard
328                        assign.guard.update(|guard| {
329                            guard.and(not_cond_wire_guard.clone())
330                        })
331                    });
332                }
333                if_group_assigns.extend(tgroup_assigns);
334                if_group_assigns.extend(fgroup_assigns);
335                if_group.borrow_mut().assignments = if_group_assigns;
336                if_group.borrow_mut().attributes = attributes.clone();
337                if_group
338            }
339            ir::StaticControl::Repeat(ir::StaticRepeat {
340                latency,
341                num_repeats,
342                body,
343                attributes,
344            }) => {
345                let repeat_group =
346                    builder.add_static_group("static_repeat", *latency);
347                // turn body into a group body_group by recursively calling inline_static_control
348                let body_group =
349                    StaticInliner::inline_static_control(body, builder);
350                assert_eq!(*latency, (num_repeats * body_group.borrow().get_latency()), "latency of static repeat is not equal to num_repeats * latency of body");
351                // the assignments in the repeat group should simply trigger the
352                // body group. So the static group will literally look like:
353                // static group static_repeat <num_repeats * body_latency> {body[go] = 1'd1;}
354                structure!( builder;
355                    let signal_on = constant(1,1);
356                );
357                let trigger_body = build_assignments!(builder;
358                    body_group["go"] = ? signal_on["out"];
359                );
360                repeat_group.borrow_mut().assignments = trigger_body.to_vec();
361                repeat_group.borrow_mut().attributes = attributes.clone();
362                repeat_group
363            }
364            ir::StaticControl::Empty(_) => unreachable!(
365                "should not call inline_static_control on empty stmt"
366            ),
367            ir::StaticControl::Invoke(inv) => {
368                dbg!(inv.comp.borrow().name());
369                todo!("implement static inlining for invokes")
370            }
371        }
372    }
373}
374
375impl Visitor for StaticInliner {
376    /// Executed after visiting the children of a [ir::Static] node.
377    fn start_static_control(
378        &mut self,
379        s: &mut ir::StaticControl,
380        comp: &mut ir::Component,
381        sigs: &LibrarySignatures,
382        _comps: &[ir::Component],
383    ) -> VisResult {
384        let mut builder = ir::Builder::new(comp, sigs);
385        let replacement_group =
386            StaticInliner::inline_static_control(s, &mut builder);
387        Ok(Action::Change(Box::new(ir::Control::from(
388            ir::StaticControl::from(replacement_group),
389        ))))
390    }
391}