calyx_opt/passes/
compile_static_interface.rs

1use super::compile_static::make_assign_dyn;
2use crate::passes::math_utilities::get_bit_width_from;
3use crate::traversal::{Action, Named, VisResult, Visitor};
4use calyx_ir as ir;
5use ir::{
6    build_assignments, guard, structure, Attributes, Guard, Nothing,
7    StaticTiming, RRC,
8};
9use itertools::Itertools;
10use std::cell::RefCell;
11use std::rc::Rc;
12
13#[derive(Default)]
14pub struct CompileStaticInterface;
15
16impl Named for CompileStaticInterface {
17    fn name() -> &'static str {
18        "compile-static-interface"
19    }
20
21    fn description() -> &'static str {
22        "Compiles Static Component Interface"
23    }
24}
25
26// Looks recursively thru guard to %[0:n] into %0 | %[1:n].
27fn separate_first_cycle(
28    guard: ir::Guard<StaticTiming>,
29) -> ir::Guard<StaticTiming> {
30    match guard {
31        ir::Guard::Info(st) => {
32            let (beg, end) = st.get_interval();
33            if beg == 0 && end != 1 {
34                let first_cycle =
35                    ir::Guard::Info(ir::StaticTiming::new((0, 1)));
36                let after = ir::Guard::Info(ir::StaticTiming::new((1, end)));
37                let cong = ir::Guard::or(first_cycle, after);
38                return cong;
39            }
40            guard
41        }
42        ir::Guard::And(l, r) => {
43            let left = separate_first_cycle(*l);
44            let right = separate_first_cycle(*r);
45            ir::Guard::and(left, right)
46        }
47        ir::Guard::Or(l, r) => {
48            let left = separate_first_cycle(*l);
49            let right = separate_first_cycle(*r);
50            ir::Guard::or(left, right)
51        }
52        ir::Guard::Not(g) => {
53            let a = separate_first_cycle(*g);
54            ir::Guard::Not(Box::new(a))
55        }
56        _ => guard,
57    }
58}
59
60// Looks recursively thru assignment's guard to %[0:n] into %0 | %[1:n].
61fn separate_first_cycle_assign(
62    assign: ir::Assignment<StaticTiming>,
63) -> ir::Assignment<StaticTiming> {
64    ir::Assignment {
65        src: assign.src,
66        dst: assign.dst,
67        attributes: assign.attributes,
68        guard: Box::new(separate_first_cycle(*assign.guard)),
69    }
70}
71
72// Used for guards in a one cycle static component.
73// Replaces %0 with comp.go.
74fn make_guard_dyn_one_cycle_static_comp(
75    guard: ir::Guard<StaticTiming>,
76    comp_sig: RRC<ir::Cell>,
77) -> ir::Guard<Nothing> {
78    match guard {
79        ir::Guard::Or(l, r) => {
80            let left =
81                make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig));
82            let right =
83                make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig));
84            ir::Guard::or(left, right)
85        }
86        ir::Guard::And(l, r) => {
87            let left =
88                make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig));
89            let right =
90                make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig));
91            ir::Guard::and(left, right)
92        }
93        ir::Guard::Not(g) => {
94            let f =
95                make_guard_dyn_one_cycle_static_comp(*g, Rc::clone(&comp_sig));
96            ir::Guard::Not(Box::new(f))
97        }
98        ir::Guard::Info(t) => {
99            match t.get_interval() {
100                (0, 1) => guard!(comp_sig["go"]),
101                _ => unreachable!("This function is implemented for 1 cycle static components, only %0 can exist as timing guard"),
102
103            }
104        }
105        ir::Guard::CompOp(op, l, r) => ir::Guard::CompOp(op, l, r),
106        ir::Guard::Port(p) => ir::Guard::Port(p),
107        ir::Guard::True => ir::Guard::True,
108    }
109}
110
111// Used for assignments in a one cycle static component.
112// Replaces %0 with comp.go in the assignment's guard.
113fn make_assign_dyn_one_cycle_static_comp(
114    assign: ir::Assignment<StaticTiming>,
115    comp_sig: RRC<ir::Cell>,
116) -> ir::Assignment<Nothing> {
117    ir::Assignment {
118        src: assign.src,
119        dst: assign.dst,
120        attributes: assign.attributes,
121        guard: Box::new(make_guard_dyn_one_cycle_static_comp(
122            *assign.guard,
123            comp_sig,
124        )),
125    }
126}
127
128impl CompileStaticInterface {
129    // Takes the assignments within a static component, and instantiates
130    // an FSM (i.e., counter) to convert %[i:j] into i<= fsm < j.
131    // Also includes logic to make fsm reset to 0 once it gets to n-1.
132    fn make_early_reset_assigns_static_component(
133        &mut self,
134        sgroup_assigns: &mut Vec<ir::Assignment<ir::StaticTiming>>,
135        latency: u64,
136        fsm: ir::RRC<ir::Cell>,
137        builder: &mut ir::Builder,
138        comp_sig: RRC<ir::Cell>,
139    ) -> Vec<ir::Assignment<Nothing>> {
140        let fsm_name = fsm.borrow().name();
141        let fsm_size = fsm
142            .borrow()
143            .find("out")
144            .unwrap_or_else(|| unreachable!("no `out` port on {fsm_name}"))
145            .borrow()
146            .width;
147        let mut assigns = sgroup_assigns
148            .drain(..)
149            .map(separate_first_cycle_assign)
150            .collect_vec();
151        let mut dyn_assigns = assigns
152            .drain(..)
153            .map(|assign| {
154                make_assign_dyn(
155                    assign,
156                    &fsm,
157                    fsm_size,
158                    builder,
159                    true,
160                    Some(Rc::clone(&comp_sig)),
161                )
162            })
163            .collect_vec();
164        let this = Rc::clone(&comp_sig);
165        structure!( builder;
166            // done hole will be undefined bc of early reset
167            let signal_on = constant(1,1);
168            let adder = prim std_add(fsm_size);
169            let const_one = constant(1, fsm_size);
170            let first_state = constant(0, fsm_size);
171            let final_state = constant(latency-1, fsm_size);
172        );
173        let g1: Guard<Nothing> = guard!(this["go"]);
174        let g2: Guard<Nothing> = guard!(fsm["out"] == first_state["out"]);
175        let trigger_guard = ir::Guard::and(g1, g2);
176        let g3: Guard<Nothing> = guard!(fsm["out"] != first_state["out"]);
177        let g4: Guard<Nothing> = guard!(fsm["out"] != final_state["out"]);
178        let incr_guard = ir::Guard::and(g3, g4);
179        let stop_guard: Guard<Nothing> =
180            guard!(fsm["out"] == final_state["out"]);
181        let fsm_incr_assigns = build_assignments!(
182          builder;
183          // Incrementsthe fsm
184          adder["left"] = ? fsm["out"];
185          adder["right"] = ? const_one["out"];
186          // Always write into fsm.
187          fsm["write_en"] = ? signal_on["out"];
188          // If fsm == 0 and comp.go is high, then we start an execution.
189          fsm["in"] = trigger_guard ? const_one["out"];
190          // If 1 < fsm < n - 1, then we unconditionally increment the fsm.
191          fsm["in"] = incr_guard ? adder["out"];
192          // If fsm == n -1 , then we reset the FSM.
193          fsm["in"] = stop_guard ? first_state["out"];
194          // Otherwise the FSM is not assigned to, so it defaults to 0.
195          // If we want, we could add an explicit assignment here that sets it
196          // to zero.
197        );
198        dyn_assigns.extend(fsm_incr_assigns);
199
200        dyn_assigns
201    }
202
203    // Makes `done` signal for promoted static<n> component.
204    fn make_done_signal_for_promoted_component(
205        &mut self,
206        fsm: ir::RRC<ir::Cell>,
207        builder: &mut ir::Builder,
208        comp_sig: RRC<ir::Cell>,
209    ) -> Vec<ir::Assignment<Nothing>> {
210        let fsm_size = fsm
211            .borrow()
212            .find("out")
213            .unwrap_or_else(|| {
214                unreachable!("no `out` port on {}", fsm.borrow().name())
215            })
216            .borrow()
217            .width;
218        structure!(builder;
219          let sig_reg = prim std_reg(1);
220          let one = constant(1, 1);
221          let zero = constant(0, 1);
222          let first_state = constant(0, fsm_size);
223        );
224        let go_guard = guard!(comp_sig["go"]);
225        let not_go_guard = !guard!(comp_sig["go"]);
226        let first_state_guard = guard!(fsm["out"] == first_state["out"]);
227        let comp_done_guard =
228            guard!(fsm["out"] == first_state["out"]) & guard!(sig_reg["out"]);
229        let assigns = build_assignments!(builder;
230          // Only write to sig_reg when fsm == 0
231          sig_reg["write_en"] = first_state_guard ? one["out"];
232          // If fsm == 0 and comp.go is high, it means we are starting an execution,
233          // so we set signal_reg to high. Note that this happens regardless of
234          // whether comp.done is high.
235          sig_reg["in"] = go_guard ? one["out"];
236          // Otherwise, we set `sig_reg` to low.
237          sig_reg["in"] = not_go_guard ? zero["out"];
238          // comp.done is high when FSM == 0 and sig_reg is high,
239          // since that means we have just finished an execution.
240          comp_sig["done"] = comp_done_guard ? one["out"];
241        );
242        assigns.to_vec()
243    }
244
245    fn make_done_signal_for_promoted_component_one_cycle(
246        &mut self,
247        builder: &mut ir::Builder,
248        comp_sig: RRC<ir::Cell>,
249    ) -> Vec<ir::Assignment<Nothing>> {
250        structure!(builder;
251          let sig_reg = prim std_reg(1);
252          let one = constant(1, 1);
253          let zero = constant(0, 1);
254        );
255        let go_guard = guard!(comp_sig["go"]);
256        let not_go = !guard!(comp_sig["go"]);
257        let signal_on_guard = guard!(sig_reg["out"]);
258        let assigns = build_assignments!(builder;
259          // For one cycle components, comp.done is just whatever comp.go
260          // was during the previous cycle.
261          // signal_reg serves as a forwarding register that delays
262          // the `go` signal for one cycle.
263          sig_reg["in"] = go_guard ? one["out"];
264          sig_reg["in"] = not_go ? zero["out"];
265          sig_reg["write_en"] = ? one["out"];
266          comp_sig["done"] = signal_on_guard ? one["out"];
267        );
268        assigns.to_vec()
269    }
270}
271
272impl Visitor for CompileStaticInterface {
273    fn start_static_control(
274        &mut self,
275        s: &mut ir::StaticControl,
276        comp: &mut ir::Component,
277        sigs: &ir::LibrarySignatures,
278        _comps: &[ir::Component],
279    ) -> VisResult {
280        if comp.is_static() && s.get_latency() > 1 {
281            // Handle components with latency > 1.
282            let latency = s.get_latency();
283            if let ir::StaticControl::Enable(sen) = s {
284                let mut builder = ir::Builder::new(comp, sigs);
285                let fsm_size = get_bit_width_from(latency + 1);
286                structure!( builder;
287                    let fsm = prim std_reg(fsm_size);
288                );
289                let mut assignments =
290                    std::mem::take(&mut sen.group.borrow_mut().assignments);
291                let comp_sig = Rc::clone(&builder.component.signature);
292                let dyn_assigns = self
293                    .make_early_reset_assigns_static_component(
294                        &mut assignments,
295                        s.get_latency(),
296                        Rc::clone(&fsm),
297                        &mut builder,
298                        Rc::clone(&comp_sig),
299                    );
300                builder.component.continuous_assignments.extend(dyn_assigns);
301                if builder.component.attributes.has(ir::BoolAttr::Promoted) {
302                    let done_assigns = self
303                        .make_done_signal_for_promoted_component(
304                            Rc::clone(&fsm),
305                            &mut builder,
306                            Rc::clone(&comp_sig),
307                        );
308                    builder
309                        .component
310                        .continuous_assignments
311                        .extend(done_assigns);
312                }
313            }
314        } else if comp.is_static() && s.get_latency() == 1 {
315            // Handle components with latency == 1.
316            if let ir::StaticControl::Enable(sen) = s {
317                let assignments =
318                    std::mem::take(&mut sen.group.borrow_mut().assignments);
319                for assign in assignments {
320                    let comp_sig = Rc::clone(&comp.signature);
321                    comp.continuous_assignments.push(
322                        make_assign_dyn_one_cycle_static_comp(assign, comp_sig),
323                    );
324                }
325                if comp.attributes.has(ir::BoolAttr::Promoted) {
326                    let mut builder = ir::Builder::new(comp, sigs);
327                    let comp_sig = Rc::clone(&builder.component.signature);
328                    let done_assigns = self
329                        .make_done_signal_for_promoted_component_one_cycle(
330                            &mut builder,
331                            comp_sig,
332                        );
333                    builder
334                        .component
335                        .continuous_assignments
336                        .extend(done_assigns);
337                }
338            }
339        }
340        Ok(Action::Continue)
341    }
342
343    fn finish(
344        &mut self,
345        comp: &mut ir::Component,
346        _sigs: &ir::LibrarySignatures,
347        _comps: &[ir::Component],
348    ) -> VisResult {
349        // Remove the control.
350        if comp.is_static() {
351            let _c = std::mem::replace(
352                &mut comp.control,
353                Rc::new(RefCell::new(ir::Control::Empty(ir::Empty {
354                    attributes: Attributes::default(),
355                }))),
356            );
357        }
358        Ok(Action::Stop)
359    }
360}