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}