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// }