calyx_opt/passes/compile_static.rs
1use crate::analysis::{
2 GraphColoring, Node, ParNodes, SingleNode, StateType, StaticFSM,
3};
4use crate::traversal::{
5 Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor,
6};
7use calyx_ir::{self as ir, Nothing, PortParent};
8use calyx_ir::{GetAttributes, guard, structure};
9use calyx_utils::{CalyxResult, Error};
10use core::panic;
11use ir::{RRC, build_assignments};
12use itertools::Itertools;
13use std::collections::{BTreeMap, HashMap, HashSet};
14use std::ops::Not;
15use std::rc::Rc;
16use std::vec;
17
18type OptionalStaticFSM = Option<ir::RRC<StaticFSM>>;
19
20/// Compiles Static Islands
21pub struct CompileStatic {
22 /// maps original static group names to the corresponding group that has an FSM that reset early
23 reset_early_map: HashMap<ir::Id, ir::Id>,
24 /// maps group that has an FSM that resets early to its dynamic "wrapper" group name.
25 wrapper_map: HashMap<ir::Id, ir::Id>,
26 /// maps fsm names to their corresponding signal_reg
27 signal_reg_map: HashMap<ir::Id, ir::Id>,
28 /// maps reset_early_group names to (fsm_identifier, fsm_first_state, final_fsm_state);
29 /// The "fsm identifier" is just the name of the fsm (if it exists) and
30 /// some other unique identifier if it doesn't exist (this works because
31 /// it is always fine to give each entry group its own completely unique identifier.)
32 fsm_info_map:
33 HashMap<ir::Id, (ir::Id, ir::Guard<Nothing>, ir::Guard<Nothing>)>,
34 /// Maps `static_group[go]` to `early_reset_group[go]`.
35 group_rewrites: ir::rewriter::PortRewriteMap,
36
37 /// Command line arguments:
38 /// Cutoff for one hot encoding. Anything larger than the cutoff becomes
39 /// binary.
40 one_hot_cutoff: u64,
41 /// Bool indicating whether to make the FSM pause (i.e., stop counting) when
42 /// offloading. In order for compilation to make sense, this parameter must
43 /// match the parameter for `static-inline`.
44 offload_pause: bool,
45 /// Bool indicating whether to greedily share the FSM registers
46 greedy_share: bool,
47}
48
49impl Named for CompileStatic {
50 fn name() -> &'static str {
51 "compile-static"
52 }
53
54 fn description() -> &'static str {
55 "compiles static sub-programs into a dynamic group"
56 }
57
58 fn opts() -> Vec<PassOpt> {
59 vec![PassOpt::new(
60 "one-hot-cutoff",
61 "The upper limit on the number of states the static FSM must have before we pick binary
62 encoding over one-hot. Defaults to 0 (i.e., always choose binary encoding)",
63 ParseVal::Num(0),
64 PassOpt::parse_num,
65 ),
66 PassOpt::new(
67 "offload-pause",
68 "Whether to pause the static FSM when offloading. Note that this
69 parameter must be in sync with the static-inliner's offload-pause
70 parameter for compilation to work correctly",
71 ParseVal::Bool(true),
72 PassOpt::parse_bool,
73 ),
74 PassOpt::new(
75 "greedy-share",
76 "Whether to greedily share the FSMs",
77 ParseVal::Bool(true),
78 PassOpt::parse_bool,
79 )
80
81 ]
82 }
83}
84
85impl ConstructVisitor for CompileStatic {
86 fn from(ctx: &ir::Context) -> CalyxResult<Self> {
87 let opts = Self::get_opts(ctx);
88
89 Ok(CompileStatic {
90 one_hot_cutoff: opts["one-hot-cutoff"].pos_num().unwrap(),
91 offload_pause: opts["offload-pause"].bool(),
92 greedy_share: opts["greedy-share"].bool(),
93 reset_early_map: HashMap::new(),
94 wrapper_map: HashMap::new(),
95 signal_reg_map: HashMap::new(),
96 fsm_info_map: HashMap::new(),
97 group_rewrites: ir::rewriter::PortRewriteMap::default(),
98 })
99 }
100
101 fn clear_data(&mut self) {
102 self.reset_early_map = HashMap::new();
103 self.wrapper_map = HashMap::new();
104 self.signal_reg_map = HashMap::new();
105 self.fsm_info_map = HashMap::new();
106 self.group_rewrites = ir::rewriter::PortRewriteMap::default();
107 }
108}
109
110impl CompileStatic {
111 /// Builds a wrapper group for group named group_name using fsm_final_state
112 /// and a signal_reg.
113 /// We set the signal_reg high on the final fsm state, since we know the
114 /// `done` signal should be high the next cycle after that.
115 /// `add_resetting_logic` is a bool; since the same FSM/signal_reg pairing
116 /// may be used for multiple static islands, and we only add resetting logic
117 /// for the signal_reg once.
118 fn build_wrapper_group(
119 fsm_final_state: ir::Guard<Nothing>,
120 group_name: &ir::Id,
121 signal_reg: ir::RRC<ir::Cell>,
122 builder: &mut ir::Builder,
123 add_reseting_logic: bool,
124 ) -> ir::RRC<ir::Group> {
125 // Get the group and fsm necessary to build the wrapper group.
126 let early_reset_group = builder
127 .component
128 .get_groups()
129 .find(*group_name)
130 .unwrap_or_else(|| {
131 unreachable!(
132 "called build_wrapper_group with {}, which is not a group",
133 group_name
134 )
135 });
136
137 structure!( builder;
138 let signal_on = constant(1, 1);
139 let signal_off = constant(0, 1);
140 );
141 // Making the rest of the guards guards:
142 // signal_reg.out
143 let signal_reg_guard: ir::Guard<ir::Nothing> =
144 guard!(signal_reg["out"]);
145 // !signal_reg.out
146 let not_signal_reg = signal_reg_guard.clone().not();
147 // <fsm in final state> & ! signal_reg.out
148 let final_state_not_signal = fsm_final_state & not_signal_reg;
149
150 // create the wrapper group for early_reset_group
151 let mut wrapper_name = group_name.clone().to_string();
152 wrapper_name.insert_str(0, "wrapper_");
153 let g = builder.add_group(wrapper_name);
154 let group_assigns = build_assignments!(
155 builder;
156 // early_reset_group[go] = 1'd1
157 early_reset_group["go"] = ? signal_on["out"];
158 // when <fsm_in_final_state> and !signal_reg, then set signal_reg to high
159 signal_reg["write_en"] = final_state_not_signal ? signal_on["out"];
160 signal_reg["in"] = final_state_not_signal ? signal_on["out"];
161 // group[done] = signal_reg.out ? 1'd1
162 g["done"] = signal_reg_guard ? signal_on["out"];
163 );
164 if add_reseting_logic {
165 // continuous assignments to reset signal_reg back to 0 when the wrapper is done
166 let continuous_assigns = build_assignments!(
167 builder;
168 // when (fsm == 0 & signal_reg is high), which is the done condition of the wrapper,
169 // reset the signal_reg back to low
170 signal_reg["write_en"] = signal_reg_guard ? signal_on["out"];
171 signal_reg["in"] = signal_reg_guard ? signal_off["out"];
172 );
173 builder.add_continuous_assignments(continuous_assigns.to_vec());
174 }
175 g.borrow_mut().assignments = group_assigns.to_vec();
176 g.borrow_mut().attributes =
177 early_reset_group.borrow().attributes.clone();
178 g
179 }
180
181 /// compile `while` whose body is `static` control such that at the end of each
182 /// iteration, the checking of condition does not incur an extra cycle of
183 /// latency.
184 /// We do this by wrapping the early reset group of the body with
185 /// another wrapper group, which sets the go signal of the early reset group
186 /// high, and is done when at the 0th cycle of each iteration, the condtion
187 /// port is done.
188 /// Note: this only works if the port for the while condition is `@stable`.
189 fn build_wrapper_group_while(
190 &self,
191 fsm_first_state: ir::Guard<Nothing>,
192 group_name: &ir::Id,
193 port: RRC<ir::Port>,
194 builder: &mut ir::Builder,
195 ) -> RRC<ir::Group> {
196 let reset_early_group = builder
197 .component
198 .find_group(*group_name)
199 .unwrap_or_else(|| {
200 unreachable!(
201 "called build_wrapper_group with {}, which is not a group",
202 group_name
203 )
204 });
205
206 let wrapper_group =
207 builder.add_group(format!("while_wrapper_{group_name}"));
208
209 structure!(
210 builder;
211 let one = constant(1, 1);
212 );
213
214 let port_parent = port.borrow().cell_parent();
215 let port_name = port.borrow().name;
216 let done_guard = guard!(port_parent[port_name]).not() & fsm_first_state;
217
218 let assignments = build_assignments!(
219 builder;
220 reset_early_group["go"] = ? one["out"];
221 wrapper_group["done"] = done_guard ? one["out"];
222 );
223
224 wrapper_group.borrow_mut().assignments.extend(assignments);
225 wrapper_group
226 }
227
228 // Get early reset group name from static control (we assume the static control
229 // is an enable).
230 fn get_reset_group_name(&self, sc: &mut ir::StaticControl) -> &ir::Id {
231 // assume that there are only static enables left.
232 // if there are any other type of static control, then error out.
233 let ir::StaticControl::Enable(s) = sc else {
234 unreachable!(
235 "Non-Enable Static Control should have been compiled away. Run {} to do this",
236 crate::passes::StaticInliner::name()
237 );
238 };
239
240 let sgroup = s.group.borrow_mut();
241 let sgroup_name = sgroup.name();
242 // get the "early reset group". It should exist, since we made an
243 // early_reset group for every static group in the component
244
245 (self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
246 unreachable!("group {} not in self.reset_early_map", sgroup_name)
247 })) as _
248 }
249}
250
251// These are the functions used to allocate FSMs to static islands through a
252// greedy coloring algorithm.
253impl CompileStatic {
254 // Given a list of `static_groups`, find the group named `name`.
255 // If there is no such group, then there is an unreachable! error.
256 fn find_static_group(
257 name: &ir::Id,
258 static_groups: &[ir::RRC<ir::StaticGroup>],
259 ) -> ir::RRC<ir::StaticGroup> {
260 Rc::clone(
261 static_groups
262 .iter()
263 .find(|static_group| static_group.borrow().name() == name)
264 .unwrap_or_else(|| {
265 unreachable!("couldn't find static group {name}")
266 }),
267 )
268 }
269
270 /// Add conflicts between all nodes of `fsm_trees` which are executing
271 /// on separate threads of a dynamic `par` block.
272 /// This function adds conflicts between nodes of separate trees.
273 fn add_par_conflicts(
274 c: &ir::Control,
275 fsm_trees: &Vec<Node>,
276 conflict_graph: &mut GraphColoring<ir::Id>,
277 ) {
278 match c {
279 ir::Control::Empty(_)
280 | ir::Control::Enable(_)
281 | ir::Control::Invoke(_)
282 | ir::Control::Static(_) => (),
283 ir::Control::Seq(seq) => {
284 for stmt in &seq.stmts {
285 Self::add_par_conflicts(stmt, fsm_trees, conflict_graph);
286 }
287 }
288 ir::Control::Repeat(ir::Repeat { body, .. })
289 | ir::Control::While(ir::While { body, .. }) => {
290 Self::add_par_conflicts(body, fsm_trees, conflict_graph)
291 }
292 ir::Control::If(if_stmt) => {
293 Self::add_par_conflicts(
294 &if_stmt.tbranch,
295 fsm_trees,
296 conflict_graph,
297 );
298 Self::add_par_conflicts(
299 &if_stmt.fbranch,
300 fsm_trees,
301 conflict_graph,
302 );
303 }
304 ir::Control::Par(par) => {
305 // `sgroup_conflict_vec` is a vec of HashSets.
306 // Each item in the vec corresponds to a par thread, and holds
307 // all of the groups executed in that thread.
308 let mut sgroup_conflict_vec: Vec<HashSet<ir::Id>> = Vec::new();
309 for stmt in &par.stmts {
310 sgroup_conflict_vec.push(HashSet::from_iter(
311 Self::get_static_enables(stmt),
312 ));
313 }
314 for (thread1_st_enables, thread2_st_enables) in
315 sgroup_conflict_vec.iter().tuple_combinations()
316 {
317 // For each static group g1 enabled in thread1 and static
318 // group g2 enabled in thread2 respectively, add a conflict
319 // each node in g1 and g2's corresponding trees.
320 for static_enable1 in thread1_st_enables {
321 for static_enable2 in thread2_st_enables {
322 // Getting tree1
323 let tree1 = fsm_trees
324 .iter()
325 .find(|tree| {
326 tree.get_group_name() == static_enable1
327 })
328 .expect("couldn't find FSM tree");
329 // Getting tree2
330 let tree2 = fsm_trees
331 .iter()
332 .find(|tree| {
333 tree.get_group_name() == static_enable2
334 })
335 .expect("couldn't find tree");
336 // Add conflict between each node in tree1 and tree2
337 for sgroup1 in tree1.get_all_nodes() {
338 for sgroup2 in tree2.get_all_nodes() {
339 conflict_graph
340 .insert_conflict(&sgroup1, &sgroup2)
341 }
342 }
343 }
344 }
345 }
346 // Necessary to add conflicts between nested pars
347 for stmt in &par.stmts {
348 Self::add_par_conflicts(stmt, fsm_trees, conflict_graph);
349 }
350 }
351 ir::Control::FSMEnable(_) => {
352 todo!("should not encounter fsm nodes")
353 }
354 }
355 }
356
357 // Gets the maximum number of repeats for the static group named
358 // `sgroup` among all trees in `tree_objects`. Most of the time, `sgroup`
359 // will only appear once but it is possible that the same group appears
360 // in more than one tree.
361 fn get_max_num_repeats(sgroup: ir::Id, tree_objects: &Vec<Node>) -> u64 {
362 let mut cur_max = 1;
363 for tree in tree_objects {
364 cur_max = std::cmp::max(
365 cur_max,
366 tree.get_max_value(&sgroup, &(|tree| tree.num_repeats)),
367 )
368 }
369 cur_max
370 }
371
372 // Gets the maximum number of repeats for the static group named
373 // `sgroup` among all trees in `tree_objects`. Most of the time, `sgroup`
374 // will only appear once but it is possible that the same group appears
375 // in more than one tree.
376 fn get_max_num_states(sgroup: ir::Id, tree_objects: &Vec<Node>) -> u64 {
377 let mut cur_max = 1;
378 for tree in tree_objects {
379 cur_max = std::cmp::max(
380 cur_max,
381 tree.get_max_value(&sgroup, &(|tree| tree.num_states)),
382 )
383 }
384 cur_max
385 }
386
387 /// Creates a graph (one node per item in `sgroup` where nodes are the `sgroup`'s
388 /// names).
389 /// Use `tree_objects` and `control` to draw conflicts between any two nodes
390 /// that could be executing in parallel, and returns a greedy coloring of the
391 /// graph.
392 pub fn get_coloring(
393 &self,
394 tree_objects: &Vec<Node>,
395 sgroups: &[ir::RRC<ir::StaticGroup>],
396 control: &mut ir::Control,
397 ) -> HashMap<ir::Id, ir::Id> {
398 if !self.greedy_share {
399 // If !greedy_share just give each sgroup its own color.
400 return sgroups
401 .iter()
402 .map(|g| (g.borrow().name(), g.borrow().name()))
403 .collect();
404 }
405 let mut conflict_graph: GraphColoring<ir::Id> =
406 GraphColoring::from(sgroups.iter().map(|g| g.borrow().name()));
407
408 // Necessary conflicts to ensure correctness
409
410 // Self::add_par_conflicts adds necessary conflicts between all nodes of
411 // trees that execute in separate threads of the same `par` block: this is
412 // adding conflicts between nodes of separate trees.
413 Self::add_par_conflicts(control, tree_objects, &mut conflict_graph);
414 for tree in tree_objects {
415 // tree.add_conflicts adds the necessary conflicts within nodes of
416 // same tree.
417 tree.add_conflicts(&mut conflict_graph);
418 }
419 // Optional conflicts to ?potentially? improve QoR
420 // for (sgroup1, sgroup2) in sgroups.iter().tuple_combinations() {
421 // let max_num_states1 =
422 // Self::get_max_num_states(sgroup1.borrow().name(), tree_objects);
423 // let max_num_repeats1 = Self::get_max_num_repeats(
424 // sgroup1.borrow().name(),
425 // tree_objects,
426 // );
427 // let max_num_states2 =
428 // Self::get_max_num_states(sgroup2.borrow().name(), tree_objects);
429 // let max_num_repeats2 = Self::get_max_num_repeats(
430 // sgroup2.borrow().name(),
431 // tree_objects,
432 // );
433 // if ((max_num_states1 == 1) != (max_num_states2 == 1))
434 // || ((max_num_repeats1) != (max_num_repeats2))
435 // {
436 // conflict_graph.insert_conflict(
437 // &sgroup1.borrow().name(),
438 // &sgroup2.borrow().name(),
439 // );
440 // }
441 // }
442
443 conflict_graph.color_greedy(None, true)
444 }
445
446 /// Given a coloring of group names, returns a Hashmap that maps:
447 /// colors -> (max num states for that color, max num repeats for color).
448 pub fn get_color_max_values(
449 coloring: &HashMap<ir::Id, ir::Id>,
450 tree_objects: &Vec<Node>,
451 ) -> HashMap<ir::Id, (u64, u64)> {
452 let mut colors_to_sgroups: HashMap<ir::Id, Vec<ir::Id>> =
453 HashMap::new();
454 // "Reverse" the coloring: instead of maping group names->colors,
455 // map colors -> group names.
456 for (group_name, color) in coloring {
457 colors_to_sgroups
458 .entry(*color)
459 .or_default()
460 .push(*group_name);
461 }
462 colors_to_sgroups
463 .into_iter()
464 .map(|(name, colors_sgroups)| {
465 // Get max num states for this color
466 let max_num_states = colors_sgroups
467 .iter()
468 .map(|gname| Self::get_max_num_states(*gname, tree_objects))
469 .max()
470 .expect("color is empty");
471 // Get max num repeats for this color
472 let max_num_repeats = colors_sgroups
473 .iter()
474 .map(|gname| {
475 Self::get_max_num_repeats(*gname, tree_objects)
476 })
477 .max()
478 .expect("color is empty");
479 (name, (max_num_states, max_num_repeats))
480 })
481 .collect()
482 }
483}
484
485impl CompileStatic {
486 /// `get_interval_from_guard` returns the interval found within guard `g`.
487 /// The tricky part is that sometimes there can be an implicit latency
488 /// `lat` that is not explicitly stated (i.e., every assignment in a
489 /// group with latency n has an implicit guard of %[0:n]). `lat` is `n`.
490 fn get_interval_from_guard(
491 g: &ir::Guard<ir::StaticTiming>,
492 lat: u64,
493 ) -> (u64, u64) {
494 match g {
495 calyx_ir::Guard::Info(static_timing_interval) => {
496 static_timing_interval.get_interval()
497 }
498 calyx_ir::Guard::Not(_)
499 | calyx_ir::Guard::CompOp(_, _, _)
500 | calyx_ir::Guard::Port(_)
501 | calyx_ir::Guard::True => (0, lat),
502 calyx_ir::Guard::And(l, r) => {
503 let ((beg1, end1), (beg2, end2)) = (
504 Self::get_interval_from_guard(l, lat),
505 Self::get_interval_from_guard(r, lat),
506 );
507 assert!(end1 - beg1 == lat || end2 - beg2 == lat);
508 if end1 - beg1 == lat {
509 (beg2, end2)
510 } else {
511 (beg1, end1)
512 }
513 }
514 ir::Guard::Or(_, _) => unreachable!(
515 "Shouldn't try to get interval from guard if there is an 'or' in the guard"
516 ),
517 }
518 }
519
520 // Given a children_sched (a sorted vec of intervals for which
521 // the children are active), builds an FSM schedule and returns it,
522 // along with the number of states the resulting FSM will have (42 in the
523 // example given below).
524 // Schedule maps cycles (i,j) -> fsm state type (i.e., what the fsm outputs).
525 // Here is an example FSM schedule:
526 // Cycles FSM State (i.e., `fsm.out`)
527 // (0..10) -> Normal[0,10) // FSM counting from 0..10
528 // (10..30) -> Offload(10) // Offloading to child
529 // (30..40) -> Normal[11, 21)
530 // (40,80) -> Offload(21)
531 // (80,100)-> Normal[22, 42)
532 //
533 // `target_latency` is the latency of the entire tree (100 in this case).
534 fn build_tree_schedule(
535 children_sched: &[(u64, u64)],
536 target_latency: u64,
537 ) -> (BTreeMap<(u64, u64), StateType>, u64) {
538 let mut fsm_schedule = BTreeMap::new();
539 let mut cur_num_states = 0;
540 let mut cur_lat = 0;
541 for (beg, end) in children_sched {
542 // Filling in the gap between children, if necessary with a
543 // `Normal` StateType.
544 if cur_lat != *beg {
545 fsm_schedule.insert(
546 (cur_lat, *beg),
547 StateType::Normal((
548 cur_num_states,
549 cur_num_states + (beg - cur_lat),
550 )),
551 );
552 cur_num_states += beg - cur_lat;
553 // cur_lat = *beg; assignment is unnecessary
554 }
555 // Inserting an Offload StateType to the schedule.
556 fsm_schedule
557 .insert((*beg, *end), StateType::Offload(cur_num_states));
558 cur_lat = *end;
559 cur_num_states += 1;
560 }
561 // Filling in the gap between the final child and the end of the group
562 // with a Normal StateType.
563 if cur_lat != target_latency {
564 fsm_schedule.insert(
565 (cur_lat, target_latency),
566 StateType::Normal((
567 cur_num_states,
568 cur_num_states + (target_latency - cur_lat),
569 )),
570 );
571 cur_num_states += target_latency - cur_lat;
572 }
573 (fsm_schedule, cur_num_states)
574 }
575
576 /// Given a static group `target_name` and vec of `static_groups`, builds a
577 /// `tree_object` for group `target_name` that repeats itself `num_repeat`
578 /// times.
579 fn build_tree_object(
580 target_name: ir::Id,
581 static_groups: &[ir::RRC<ir::StaticGroup>],
582 num_repeats: u64,
583 ) -> Node {
584 // Find the group that will serve as the root of the tree.
585 let target_group = static_groups
586 .iter()
587 .find(|sgroup| sgroup.borrow().name() == target_name)
588 .unwrap();
589 // Children of the root of the tree.
590 let mut children_vec = vec![];
591
592 let target_group_ref = target_group.borrow();
593 for assign in &target_group_ref.assignments {
594 // Looking for static_child[go] = %[i:j] ? 1'd1; to build children.
595 // This lets us know that `static_child` is executing from cycles
596 // i through j.
597 match &assign.dst.borrow().parent {
598 PortParent::Cell(_) => {
599 if target_group_ref.attributes.has(ir::BoolAttr::ParCtrl) {
600 panic!("")
601 }
602 }
603 PortParent::Group(_) | PortParent::FSM(_) => panic!(""),
604 PortParent::StaticGroup(sgroup) => {
605 assert!(assign.src.borrow().is_constant(1, 1));
606 let (beg, end) = Self::get_interval_from_guard(
607 &assign.guard,
608 target_group.borrow().get_latency(),
609 );
610 let name: calyx_ir::Id = sgroup.upgrade().borrow().name();
611 // Need the following lines to determine `num_repeats`
612 // for the child.
613 let target_child_latency =
614 Self::get_sgroup_latency(name, static_groups);
615 let child_execution_time = end - beg;
616 assert!(
617 child_execution_time % target_child_latency == 0,
618 "child will execute only part of an iteration"
619 );
620 let child_num_repeats =
621 child_execution_time / target_child_latency;
622 // Recursively build a tree for the child.
623 children_vec.push((
624 Self::build_tree_object(
625 name,
626 static_groups,
627 child_num_repeats,
628 ),
629 (beg, end),
630 ));
631 }
632 }
633 }
634
635 if target_group_ref.attributes.has(ir::BoolAttr::ParCtrl) {
636 // If we are in a par group, then the "children" are actually
637 // threads that should all start at 0.
638 assert!(children_vec.iter().all(|(_, (beg, _))| *beg == 0));
639 Node::Par(ParNodes {
640 group_name: target_name,
641 threads: children_vec,
642 latency: target_group_ref.latency,
643 num_repeats,
644 })
645 } else {
646 // If we are in a regular group, then the children should be
647 // non-overlapping.
648 children_vec.sort_by_key(|(_, interval)| *interval);
649 assert!(Self::are_ranges_non_overlapping(&children_vec));
650 let (fsm_schedule, num_states) = Self::build_tree_schedule(
651 &children_vec
652 .iter()
653 .map(|(_, interval)| *interval)
654 .collect_vec(),
655 target_group_ref.latency,
656 );
657 Node::Single(SingleNode {
658 latency: target_group_ref.latency,
659 fsm_cell: None,
660 iter_count_cell: None,
661 root: (target_name, vec![]),
662 fsm_schedule,
663 children: children_vec,
664 num_repeats,
665 num_states,
666 })
667 }
668 }
669
670 /// Builds a dummy tree, solely for the purposes of determining conflicts
671 /// so we can greedily color when assigning FSMs. This should only be occuring
672 /// when we count during offloading (i.e., don't pause).
673 /// This tree should never actually be turned into hardware!! (Thd trees that we
674 /// build here do not make sense if you want to actually do that.)
675 /// We can't call `build_tree_object` on this because that looks for the
676 /// `par` attribute, which isn't present when we're counting.
677 fn build_dummy_tree(
678 target_name: ir::Id,
679 static_groups: &[ir::RRC<ir::StaticGroup>],
680 ) -> Node {
681 // Find the group that will serve as the root of the tree.
682 let target_group = static_groups
683 .iter()
684 .find(|sgroup| sgroup.borrow().name() == target_name)
685 .unwrap();
686 let mut children_vec = vec![];
687 let target_group_ref = target_group.borrow();
688 assert!(
689 !target_group_ref.attributes.has(ir::BoolAttr::ParCtrl),
690 "ParCtrl attribute is not compatible with building dummy trees"
691 );
692 for assign in &target_group_ref.assignments {
693 // Looking for static_child[go] = %[i:j] ? 1'd1; to build children.
694 match &assign.dst.borrow().parent {
695 PortParent::Cell(_) => (),
696 PortParent::Group(_) | PortParent::FSM(_) => unreachable!(""),
697 PortParent::StaticGroup(sgroup) => {
698 assert!(assign.src.borrow().is_constant(1, 1));
699 let (beg, end) = Self::get_interval_from_guard(
700 &assign.guard,
701 target_group.borrow().get_latency(),
702 );
703
704 let name: calyx_ir::Id = sgroup.upgrade().borrow().name();
705 children_vec.push((
706 Self::build_dummy_tree(name, static_groups),
707 (beg, end),
708 ));
709 }
710 }
711 }
712
713 children_vec.sort_by_key(|(_, interval)| *interval);
714 Node::Single(SingleNode {
715 latency: target_group_ref.latency,
716 fsm_cell: None,
717 iter_count_cell: None,
718 root: (target_name, vec![]),
719 fsm_schedule: BTreeMap::new(),
720 children: children_vec,
721 num_repeats: 1,
722 num_states: target_group_ref.latency,
723 })
724 }
725
726 /// Builds "trees" but just make them single nodes that never offload.
727 /// This is the original strategy that we used.
728 fn build_single_node(
729 name: ir::Id,
730 static_groups: &[ir::RRC<ir::StaticGroup>],
731 ) -> Node {
732 let target_group = static_groups
733 .iter()
734 .find(|sgroup| sgroup.borrow().name() == name)
735 .unwrap();
736 let target_group_ref = target_group.borrow();
737 assert!(
738 !target_group_ref.attributes.has(ir::BoolAttr::ParCtrl),
739 "ParCtrl attribute is not compatible with building a single node"
740 );
741
742 Node::Single(SingleNode {
743 latency: target_group_ref.latency,
744 fsm_cell: None,
745 iter_count_cell: None,
746 root: (name, vec![]),
747 fsm_schedule: vec![(
748 (0, target_group_ref.latency),
749 StateType::Normal((0, target_group_ref.latency)),
750 )]
751 .into_iter()
752 .collect(),
753 children: vec![],
754 num_repeats: 1,
755 num_states: target_group_ref.latency,
756 })
757 }
758
759 /// Search through `static_groups` and get latency of sgroup named `name`
760 fn get_sgroup_latency(
761 name: ir::Id,
762 static_groups: &[ir::RRC<ir::StaticGroup>],
763 ) -> u64 {
764 static_groups
765 .iter()
766 .find(|sgroup| sgroup.borrow().name() == name)
767 .expect("couldn't find static group")
768 .borrow()
769 .get_latency()
770 }
771
772 // Given a vec of tuples (i,j) sorted by the first element (i.e., `i`) checks
773 // whether the ranges do not overlap.
774 fn are_ranges_non_overlapping(ranges: &[(Node, (u64, u64))]) -> bool {
775 if ranges.is_empty() {
776 return true;
777 }
778 for i in 0..ranges.len() - 1 {
779 let (_, (_, end1)) = ranges[i];
780 let (_, (start2, _)) = ranges[i + 1];
781 // Ensure that the current range's end is less than or equal to the next range's start
782 if end1 > start2 {
783 return false;
784 }
785 }
786 true
787 }
788
789 // Get a vec of all static groups that were "enabled" in `ctrl`.
790 fn get_static_enables(ctrl: &ir::Control) -> Vec<ir::Id> {
791 match ctrl {
792 ir::Control::Seq(ir::Seq { stmts, .. })
793 | ir::Control::Par(ir::Par { stmts, .. }) => stmts
794 .iter()
795 .flat_map(Self::get_static_enables)
796 .collect_vec(),
797 ir::Control::Empty(_)
798 | ir::Control::Enable(_)
799 | ir::Control::Invoke(_) => vec![],
800 ir::Control::If(c) => {
801 let mut tbranch_res = Self::get_static_enables(&c.tbranch);
802 let fbranch_res = Self::get_static_enables(&c.fbranch);
803 tbranch_res.extend(fbranch_res);
804 tbranch_res
805 }
806 ir::Control::Repeat(ir::Repeat { body, .. })
807 | ir::Control::While(ir::While { body, .. }) => {
808 Self::get_static_enables(body)
809 }
810 ir::Control::Static(sc) => {
811 let ir::StaticControl::Enable(s) = sc else {
812 unreachable!(
813 "Non-Enable Static Control should have been compiled away. Run {} to do this",
814 crate::passes::StaticInliner::name()
815 );
816 };
817 vec![s.group.borrow().name()]
818 }
819 ir::Control::FSMEnable(_) => {
820 todo!("should not encounter fsm nodes")
821 }
822 }
823 }
824}
825
826// These are the functions used to compile for the static *component* interface,
827// which (annoyingly) only needs a go signal fro %0, compared to %[0:n] for
828// static groups.
829impl CompileStatic {
830 // Used for guards in a one cycle static component.
831 // Replaces %0 with comp.go.
832 fn make_guard_dyn_one_cycle_static_comp(
833 guard: ir::Guard<ir::StaticTiming>,
834 comp_sig: RRC<ir::Cell>,
835 ) -> ir::Guard<ir::Nothing> {
836 match guard {
837 ir::Guard::Or(l, r) => {
838 let left = Self::make_guard_dyn_one_cycle_static_comp(
839 *l,
840 Rc::clone(&comp_sig),
841 );
842 let right = Self::make_guard_dyn_one_cycle_static_comp(
843 *r,
844 Rc::clone(&comp_sig),
845 );
846 ir::Guard::or(left, right)
847 }
848 ir::Guard::And(l, r) => {
849 let left = Self::make_guard_dyn_one_cycle_static_comp(
850 *l,
851 Rc::clone(&comp_sig),
852 );
853 let right = Self::make_guard_dyn_one_cycle_static_comp(
854 *r,
855 Rc::clone(&comp_sig),
856 );
857 ir::Guard::and(left, right)
858 }
859 ir::Guard::Not(g) => {
860 let f = Self::make_guard_dyn_one_cycle_static_comp(
861 *g,
862 Rc::clone(&comp_sig),
863 );
864 ir::Guard::Not(Box::new(f))
865 }
866 ir::Guard::Info(t) => match t.get_interval() {
867 (0, 1) => guard!(comp_sig["go"]),
868 _ => unreachable!(
869 "This function is implemented for 1 cycle static components, only %0 can exist as timing guard"
870 ),
871 },
872 ir::Guard::CompOp(op, l, r) => ir::Guard::CompOp(op, l, r),
873 ir::Guard::Port(p) => ir::Guard::Port(p),
874 ir::Guard::True => ir::Guard::True,
875 }
876 }
877
878 // Used for assignments in a one cycle static component.
879 // Replaces %0 with comp.go in the assignment's guard.
880 fn make_assign_dyn_one_cycle_static_comp(
881 assign: ir::Assignment<ir::StaticTiming>,
882 comp_sig: RRC<ir::Cell>,
883 ) -> ir::Assignment<ir::Nothing> {
884 ir::Assignment {
885 src: assign.src,
886 dst: assign.dst,
887 attributes: assign.attributes,
888 guard: Box::new(Self::make_guard_dyn_one_cycle_static_comp(
889 *assign.guard,
890 comp_sig,
891 )),
892 }
893 }
894
895 // Makes `done` signal for promoted static<n> component.
896 fn make_done_signal_for_promoted_component(
897 fsm_tree: &mut Node,
898 builder: &mut ir::Builder,
899 comp_sig: RRC<ir::Cell>,
900 ) -> Vec<ir::Assignment<ir::Nothing>> {
901 let first_state_guard = fsm_tree.query_between((0, 1), builder);
902 structure!(builder;
903 let sig_reg = prim std_reg(1);
904 let one = constant(1, 1);
905 let zero = constant(0, 1);
906 );
907 let go_guard = guard!(comp_sig["go"]);
908 let not_go_guard = !guard!(comp_sig["go"]);
909 let comp_done_guard =
910 first_state_guard.clone().and(guard!(sig_reg["out"]));
911 let assigns = build_assignments!(builder;
912 // Only write to sig_reg when fsm == 0
913 sig_reg["write_en"] = first_state_guard ? one["out"];
914 // If fsm == 0 and comp.go is high, it means we are starting an execution,
915 // so we set signal_reg to high. Note that this happens regardless of
916 // whether comp.done is high.
917 sig_reg["in"] = go_guard ? one["out"];
918 // Otherwise, we set `sig_reg` to low.
919 sig_reg["in"] = not_go_guard ? zero["out"];
920 // comp.done is high when FSM == 0 and sig_reg is high,
921 // since that means we have just finished an execution.
922 comp_sig["done"] = comp_done_guard ? one["out"];
923 );
924 assigns.to_vec()
925 }
926
927 // Makes a done signal for a one-cycle static component.
928 // Essentially you just have to use a one-cycle delay register that
929 // takes the `go` signal as input.
930 fn make_done_signal_for_promoted_component_one_cycle(
931 builder: &mut ir::Builder,
932 comp_sig: RRC<ir::Cell>,
933 ) -> Vec<ir::Assignment<ir::Nothing>> {
934 structure!(builder;
935 let sig_reg = prim std_reg(1);
936 let one = constant(1, 1);
937 let zero = constant(0, 1);
938 );
939 let go_guard = guard!(comp_sig["go"]);
940 let not_go = !guard!(comp_sig["go"]);
941 let signal_on_guard = guard!(sig_reg["out"]);
942 let assigns = build_assignments!(builder;
943 // For one cycle components, comp.done is just whatever comp.go
944 // was during the previous cycle.
945 // signal_reg serves as a forwarding register that delays
946 // the `go` signal for one cycle.
947 sig_reg["in"] = go_guard ? one["out"];
948 sig_reg["in"] = not_go ? zero["out"];
949 sig_reg["write_en"] = ? one["out"];
950 comp_sig["done"] = signal_on_guard ? one["out"];
951 );
952 assigns.to_vec()
953 }
954
955 // `fsm_tree` should be the top-level tree in the components.
956 // `static_groups` are the component's static groups.
957 // The assignments are removed from `sgroup` and placed into
958 // `builder.component`'s continuous assignments.
959 fn compile_static_interface(
960 &mut self,
961 fsm_tree: &mut Node,
962 static_groups: &mut Vec<ir::RRC<ir::StaticGroup>>,
963 coloring: &HashMap<ir::Id, ir::Id>,
964 colors_to_max_values: &HashMap<ir::Id, (u64, u64)>,
965 colors_to_fsm: &mut HashMap<
966 ir::Id,
967 (OptionalStaticFSM, OptionalStaticFSM),
968 >,
969 builder: &mut ir::Builder,
970 ) -> calyx_utils::CalyxResult<()> {
971 if fsm_tree.get_latency() > 1 {
972 // Find top-level static group.
973 let sgroup = Self::find_static_group(
974 &fsm_tree.get_root_name(),
975 static_groups,
976 );
977 // Perform some preprocessing on the assignments
978 // (in particular, transform %[0:n] into %0 | %[1:n])
979 for assign in &mut sgroup.borrow_mut().assignments {
980 Node::preprocess_static_interface_assigns(
981 assign,
982 Rc::clone(&builder.component.signature),
983 );
984 }
985
986 let comp_go = ir::Guard::port(
987 builder
988 .component
989 .signature
990 .borrow()
991 .find_unique_with_attr(ir::NumAttr::Go)?
992 .unwrap(),
993 );
994
995 // Realize the fsm tree in hardware.
996 fsm_tree.instantiate_fsms(
997 builder,
998 coloring,
999 colors_to_max_values,
1000 colors_to_fsm,
1001 self.one_hot_cutoff,
1002 );
1003 fsm_tree.count_to_n(builder, Some(comp_go));
1004 fsm_tree.realize(
1005 false,
1006 static_groups,
1007 &mut self.reset_early_map,
1008 &mut self.fsm_info_map,
1009 &mut self.group_rewrites,
1010 builder,
1011 );
1012 // Add root's assignments as continuous assignments, execpt for the
1013 // `group[done]` assignments.
1014 builder.component.continuous_assignments.extend(
1015 fsm_tree.take_root_assigns().into_iter().filter(|assign| {
1016 let dst = assign.dst.borrow();
1017 match dst.parent {
1018 PortParent::Cell(_) => true,
1019 // Don't add assignment to `group[done]`
1020 PortParent::Group(_) => dst.name != "done",
1021 PortParent::StaticGroup(_) => true,
1022 PortParent::FSM(_) => unreachable!(),
1023 }
1024 }),
1025 );
1026 let comp_sig = Rc::clone(&builder.component.signature);
1027 if builder.component.attributes.has(ir::BoolAttr::Promoted) {
1028 // If necessary, add the logic to produce a done signal.
1029 let done_assigns =
1030 Self::make_done_signal_for_promoted_component(
1031 fsm_tree, builder, comp_sig,
1032 );
1033 builder
1034 .component
1035 .continuous_assignments
1036 .extend(done_assigns);
1037 }
1038 } else {
1039 // Handle components with latency == 1.
1040 // In this case, we don't need an FSM; we just guard the assignments
1041 // with comp.go.
1042 let sgroup = Self::find_static_group(
1043 &fsm_tree.get_root_name(),
1044 static_groups,
1045 );
1046 for (child, _) in fsm_tree.get_children() {
1047 // We can ignore any static timing guards in the children,
1048 // since we know the latency is 1. That is why we call
1049 // `convert_assignments_type`.
1050 child.realize(
1051 true,
1052 static_groups,
1053 &mut self.reset_early_map,
1054 &mut self.fsm_info_map,
1055 &mut self.group_rewrites,
1056 builder,
1057 )
1058 }
1059 let assignments =
1060 std::mem::take(&mut sgroup.borrow_mut().assignments);
1061 for mut assign in assignments {
1062 // Make `assignments` continuous and replace %[0:1] with `comp.go`
1063 let comp_sig = Rc::clone(&builder.component.signature);
1064 assign.guard.update(|g| g.and(guard!(comp_sig["go"])));
1065 builder.component.continuous_assignments.push(
1066 Self::make_assign_dyn_one_cycle_static_comp(
1067 assign, comp_sig,
1068 ),
1069 );
1070 }
1071 if builder.component.attributes.has(ir::BoolAttr::Promoted) {
1072 // Need to add a done signal if this component was promoted.
1073 let comp_sig = Rc::clone(&builder.component.signature);
1074 let done_assigns =
1075 Self::make_done_signal_for_promoted_component_one_cycle(
1076 builder, comp_sig,
1077 );
1078 builder
1079 .component
1080 .continuous_assignments
1081 .extend(done_assigns);
1082 };
1083 };
1084 Ok(())
1085 }
1086}
1087
1088impl Visitor for CompileStatic {
1089 fn start(
1090 &mut self,
1091 comp: &mut ir::Component,
1092 sigs: &ir::LibrarySignatures,
1093 _comps: &[ir::Component],
1094 ) -> VisResult {
1095 // Drain static groups of component
1096 let mut sgroups: Vec<ir::RRC<ir::StaticGroup>> =
1097 comp.get_static_groups_mut().drain().collect();
1098
1099 let mut builder = ir::Builder::new(comp, sigs);
1100 // Get a vec of all groups that are enabled in comp's control.
1101 let static_enable_ids =
1102 Self::get_static_enables(&builder.component.control.borrow());
1103 // Build one tree object per static enable.
1104 // Even if we don't offload, we still need to build trees to
1105 // determine coloring.
1106 let default_tree_objects = static_enable_ids
1107 .iter()
1108 .map(|id| {
1109 if self.offload_pause {
1110 Self::build_tree_object(*id, &sgroups, 1)
1111 } else {
1112 // If we're not offloading, then we should build dummy trees
1113 // that are just used to determine coloring.
1114 // This is basically the same thing as `build_tree_object`,
1115 // but doesn't check the `ParCtrl` attribute. I think
1116 // we could reduce code size by merging this function with
1117 // `build_tree_object`.
1118 Self::build_dummy_tree(*id, &sgroups)
1119 }
1120 })
1121 .collect_vec();
1122
1123 // The first thing is to assign FSMs -> static islands.
1124 // We sometimes assign the same FSM to different static islands
1125 // to reduce register usage. We do this by getting greedy coloring.
1126 let coloring: HashMap<ir::Id, ir::Id> = self.get_coloring(
1127 &default_tree_objects,
1128 &sgroups,
1129 &mut builder.component.control.borrow_mut(),
1130 );
1131 // We need the max_num_states and max_num_repeats for each
1132 // color so we know how many bits the corresponding registers should get.
1133 let colors_to_max_values =
1134 Self::get_color_max_values(&coloring, &default_tree_objects);
1135 let mut colors_to_fsms: HashMap<
1136 ir::Id,
1137 (OptionalStaticFSM, OptionalStaticFSM),
1138 > = HashMap::new();
1139
1140 let mut tree_objects = if self.offload_pause {
1141 default_tree_objects
1142 } else {
1143 // Build simple trees if we're not offloading (i.e., build trees
1144 // that just consist of a single node.)
1145 // Note that these trees would not correctly draw conflicts between
1146 // nodes for coloring.
1147 let mut simple_trees = vec![];
1148 let sgroup_names = sgroups
1149 .iter()
1150 .map(|sgroup| sgroup.borrow().name())
1151 .collect_vec();
1152 for name in sgroup_names {
1153 simple_trees.push(Self::build_single_node(name, &sgroups))
1154 }
1155 simple_trees
1156 };
1157
1158 // Static components have a different interface than static groups.
1159 // If we have a static component, we have to compile the top-level
1160 // island (this island should be a group by now and corresponds
1161 // to the the entire control of the component) differently.
1162 // This island might invoke other static groups-- these static groups
1163 // should still follow the group interface.
1164 let top_level_sgroup = if builder.component.is_static() {
1165 let comp_control = builder.component.control.borrow();
1166 match &*comp_control {
1167 ir::Control::Static(ir::StaticControl::Enable(sen)) => {
1168 Some(sen.group.borrow().name())
1169 }
1170 _ => {
1171 return Err(Error::malformed_control(format!(
1172 "Non-Enable Static Control should have been compiled away. Run {} to do this",
1173 crate::passes::StaticInliner::name()
1174 )));
1175 }
1176 }
1177 } else {
1178 None
1179 };
1180 // Make each tree count to n.
1181 for tree in &mut tree_objects {
1182 // Check whether we are compiling the top level static island.
1183 let static_component_interface = match top_level_sgroup {
1184 None => false,
1185 // For the top level group, sch.static_groups should really only
1186 // have one group--the top level group.
1187 Some(top_level_group) => {
1188 tree.get_group_name() == top_level_group
1189 }
1190 };
1191 // Static component/groups have different interfaces
1192 if static_component_interface {
1193 // Compile top level static group differently.
1194 self.compile_static_interface(
1195 tree,
1196 &mut sgroups,
1197 &coloring,
1198 &colors_to_max_values,
1199 &mut colors_to_fsms,
1200 &mut builder,
1201 )?;
1202 } else {
1203 // Otherwise just instantiate the tree to hardware.
1204 tree.instantiate_fsms(
1205 &mut builder,
1206 &coloring,
1207 &colors_to_max_values,
1208 &mut colors_to_fsms,
1209 self.one_hot_cutoff,
1210 );
1211 tree.count_to_n(&mut builder, None);
1212 tree.realize(
1213 false,
1214 &sgroups,
1215 &mut self.reset_early_map,
1216 &mut self.fsm_info_map,
1217 &mut self.group_rewrites,
1218 &mut builder,
1219 );
1220 }
1221 }
1222
1223 // Rewrite static_group[go] to early_reset_group[go]
1224 // don't have to worry about writing static_group[done] b/c static
1225 // groups don't have done holes.
1226 comp.for_each_assignment(|assign| {
1227 assign.for_each_port(|port| {
1228 self.group_rewrites
1229 .get(&port.borrow().canonical())
1230 .map(Rc::clone)
1231 })
1232 });
1233
1234 // Add the static groups back to the component.
1235 comp.get_static_groups_mut().append(sgroups.into_iter());
1236
1237 Ok(Action::Continue)
1238 }
1239
1240 /// Executed after visiting the children of a [ir::Static] node.
1241 fn start_static_control(
1242 &mut self,
1243 sc: &mut ir::StaticControl,
1244 comp: &mut ir::Component,
1245 sigs: &ir::LibrarySignatures,
1246 _comps: &[ir::Component],
1247 ) -> VisResult {
1248 // The main purpose of this method is inserting wrappers / singal registers
1249 // when appropriate.
1250
1251 // No need to build wrapper for static component interface
1252 if comp.is_static() {
1253 return Ok(Action::Continue);
1254 }
1255 // Assume that there are only static enables left.
1256 // If there are any other type of static control, then error out.
1257 let ir::StaticControl::Enable(s) = sc else {
1258 return Err(Error::malformed_control(format!(
1259 "Non-Enable Static Control should have been compiled away. Run {} to do this",
1260 crate::passes::StaticInliner::name()
1261 )));
1262 };
1263
1264 let sgroup = s.group.borrow_mut();
1265 let sgroup_name = sgroup.name();
1266 // get the "early reset group". It should exist, since we made an
1267 // early_reset group for every static group in the component
1268 let early_reset_name =
1269 self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
1270 unreachable!(
1271 "{}'s early reset group has not been created",
1272 sgroup_name
1273 )
1274 });
1275 // check if we've already built the wrapper group for early_reset_group
1276 // if so, we can just use that, otherwise, we must build the wrapper group
1277 let group_choice = match self.wrapper_map.get(early_reset_name) {
1278 None => {
1279 // create the builder/cells that we need to create wrapper group
1280 let mut builder = ir::Builder::new(comp, sigs);
1281 let (fsm_name, _, fsm_final_state) = self.fsm_info_map.get(early_reset_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", early_reset_name));
1282 // If we've already made a wrapper for a group that uses the same
1283 // FSM, we can reuse the signal_reg. Otherwise, we must
1284 // instantiate a new signal_reg.
1285 let wrapper = match self.signal_reg_map.get(fsm_name) {
1286 None => {
1287 // Need to build the signal_reg and the continuous
1288 // assignment that resets the signal_reg
1289 structure!( builder;
1290 let signal_reg = prim std_reg(1);
1291 );
1292 self.signal_reg_map
1293 .insert(*fsm_name, signal_reg.borrow().name());
1294 Self::build_wrapper_group(
1295 fsm_final_state.clone(),
1296 early_reset_name,
1297 signal_reg,
1298 &mut builder,
1299 true,
1300 )
1301 }
1302 Some(reg_name) => {
1303 // Already_built the signal_reg.
1304 // We don't need to add continuous assignments
1305 // that resets signal_reg.
1306 let signal_reg = builder
1307 .component
1308 .find_cell(*reg_name)
1309 .unwrap_or_else(|| {
1310 unreachable!("signal reg {reg_name} found")
1311 });
1312 Self::build_wrapper_group(
1313 fsm_final_state.clone(),
1314 early_reset_name,
1315 signal_reg,
1316 &mut builder,
1317 false,
1318 )
1319 }
1320 };
1321 self.wrapper_map
1322 .insert(*early_reset_name, wrapper.borrow().name());
1323 wrapper
1324 }
1325 Some(name) => comp.find_group(*name).unwrap(),
1326 };
1327
1328 let mut e = ir::Control::enable(group_choice);
1329 let attrs = std::mem::take(&mut s.attributes);
1330 *e.get_mut_attributes() = attrs;
1331 Ok(Action::Change(Box::new(e)))
1332 }
1333
1334 /// If while body is static, then we want to make sure that the while
1335 /// body does not take the extra cycle incurred by the done condition
1336 /// So we replace the while loop with `enable` of a wrapper group
1337 /// that sets the go signal of the static group in the while loop body high
1338 /// (all static control should be compiled into static groups by
1339 /// `static_inliner` now). The done signal of the wrapper group should be
1340 /// the condition that the fsm of the while body is %0 and the port signal
1341 /// is 1'd0.
1342 /// For example, we replace
1343 /// ```
1344 /// wires {
1345 /// static group A<1> {
1346 /// ...
1347 /// }
1348 /// ...
1349 /// }
1350 /// control {
1351 /// while l.out {
1352 /// A;
1353 /// }
1354 /// }
1355 /// ```
1356 /// with
1357 /// ```
1358 /// wires {
1359 /// group early_reset_A {
1360 /// ...
1361 /// }
1362 ///
1363 /// group while_wrapper_early_reset_A {
1364 /// early_reset_A[go] = 1'd1;
1365 /// while_wrapper_early_reset_A[done] = !l.out & fsm.out == 1'd0 ? 1'd1;
1366 /// }
1367 /// }
1368 /// control {
1369 /// while_wrapper_early_reset_A;
1370 /// }
1371 /// ```
1372 fn start_while(
1373 &mut self,
1374 s: &mut ir::While,
1375 comp: &mut ir::Component,
1376 sigs: &ir::LibrarySignatures,
1377 _comps: &[ir::Component],
1378 ) -> VisResult {
1379 if s.cond.is_none() {
1380 if let ir::Control::Static(sc) = &mut *(s.body) {
1381 let mut builder = ir::Builder::new(comp, sigs);
1382 let reset_group_name = self.get_reset_group_name(sc);
1383
1384 // Get fsm for reset_group
1385 let (_, fsm_first_state, _) = self.fsm_info_map.get(reset_group_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", reset_group_name));
1386 let wrapper_group = self.build_wrapper_group_while(
1387 fsm_first_state.clone(),
1388 reset_group_name,
1389 Rc::clone(&s.port),
1390 &mut builder,
1391 );
1392 let c = ir::Control::enable(wrapper_group);
1393 return Ok(Action::change(c));
1394 }
1395 }
1396
1397 Ok(Action::Continue)
1398 }
1399
1400 fn finish(
1401 &mut self,
1402 comp: &mut ir::Component,
1403 _sigs: &ir::LibrarySignatures,
1404 _comps: &[ir::Component],
1405 ) -> VisResult {
1406 // make sure static groups have no assignments, since
1407 // we should have already drained the assignments in static groups
1408 // for g in comp.get_static_groups() {
1409 // if !g.borrow().assignments.is_empty() {
1410 // unreachable!("Should have converted all static groups to dynamic. {} still has assignments in it. It's possible that you may need to run {} to remove dead groups and get rid of this error.", g.borrow().name(), crate::passes::DeadGroupRemoval::name());
1411 // }
1412 // }
1413 // remove all static groups
1414 comp.get_static_groups_mut().retain(|_| false);
1415
1416 // Remove control if static component
1417 if comp.is_static() {
1418 comp.control = ir::rrc(ir::Control::empty())
1419 }
1420
1421 Ok(Action::Continue)
1422 }
1423}