1use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor};
2use crate::traversal::{DiagnosticContext, DiagnosticPass, DiagnosticResult};
3use calyx_ir::{
4 self as ir, Cell, CellType, Component, GetAttributes, LibrarySignatures,
5 RESERVED_NAMES,
6};
7use calyx_ir::{BoolAttr, Seq};
8use calyx_utils::{CalyxResult, Error, WithPos};
9use ir::Nothing;
10use ir::StaticTiming;
11use itertools::Itertools;
12use linked_hash_map::LinkedHashMap;
13use std::collections::HashMap;
14use std::collections::HashSet;
15
16fn port_is_static_prim(port: &ir::Port) -> bool {
20 let parent_cell = match &port.parent {
22 ir::PortParent::Cell(cell_wref) => cell_wref.upgrade(),
23 ir::PortParent::Group(_)
24 | ir::PortParent::StaticGroup(_)
25 | ir::PortParent::FSM(_) => {
26 return false;
27 }
28 };
29 match parent_cell.borrow().prototype {
36 ir::CellType::Primitive { latency, .. } => latency.is_some(),
37 ir::CellType::Component { .. }
38 | ir::CellType::ThisComponent
39 | ir::CellType::Constant { .. } => false,
40 }
41}
42
43#[derive(Default)]
44struct ActiveAssignments {
45 assigns: Vec<ir::Assignment<Nothing>>,
47 num_assigns: Vec<usize>,
49}
50impl ActiveAssignments {
51 pub fn push(&mut self, assign: &[ir::Assignment<Nothing>]) {
53 let prev_size = self.assigns.len();
54 self.assigns.extend(assign.iter().cloned());
55 self.num_assigns.push(self.assigns.len() - prev_size);
57 }
58
59 pub fn pop(&mut self) {
61 let num_assigns = self.num_assigns.pop().unwrap();
62 self.assigns.truncate(self.assigns.len() - num_assigns);
63 }
64
65 pub fn iter(&self) -> impl Iterator<Item = &ir::Assignment<Nothing>> {
66 self.assigns.iter()
67 }
68}
69
70pub struct WellFormed {
81 reserved_names: HashSet<ir::Id>,
83 used_groups: HashSet<ir::Id>,
85 used_comb_groups: HashSet<ir::Id>,
87 ref_cells: HashMap<ir::Id, LinkedHashMap<ir::Id, Cell>>,
89 active_comb: ActiveAssignments,
91 has_done_hole: HashSet<ir::Id>,
93 diag: DiagnosticContext,
95}
96
97impl std::fmt::Debug for WellFormed {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("WellFormed")
100 .field("has_done_hole", &self.has_done_hole)
101 .field("diag", &self.diag)
102 .finish_non_exhaustive()
103 }
104}
105
106enum Invoke<'a> {
107 StaticInvoke(&'a ir::StaticInvoke),
108 Invoke(&'a ir::Invoke),
109}
110
111impl Invoke<'_> {
112 fn get_ref_cells(&self) -> &Vec<(ir::Id, ir::RRC<Cell>)> {
113 match self {
114 Invoke::StaticInvoke(s) => &s.ref_cells,
115 Invoke::Invoke(s) => &s.ref_cells,
116 }
117 }
118
119 fn get_attributes(&self) -> &ir::Attributes {
120 match self {
121 Invoke::StaticInvoke(s) => s.get_attributes(),
122 Invoke::Invoke(s) => s.get_attributes(),
123 }
124 }
125}
126
127fn require_subtype(
128 invoke: Invoke,
129 self_ref_cells: &HashMap<ir::Id, LinkedHashMap<ir::Id, Cell>>,
130 id: &ir::Id,
131) -> CalyxResult<()> {
132 let cell_map = &self_ref_cells[id];
133 let mut mentioned_cells = HashSet::new();
134 for (outcell, incell) in invoke.get_ref_cells().iter() {
135 if let Some(oc) = cell_map.get(outcell) {
136 if !subtype(oc, &incell.borrow()) {
137 return Err(Error::malformed_control(format!(
138 "The type passed in `{}` is not a subtype of the expected type `{}`.",
139 incell.borrow().prototype.surface_name().unwrap(),
140 oc.prototype.surface_name().unwrap()
141 ))
142 .with_pos(invoke.get_attributes()));
143 } else {
144 mentioned_cells.insert(outcell);
145 }
146 } else {
147 return Err(Error::malformed_control(format!(
148 "{id} does not have ref cell named {outcell}"
149 )));
150 }
151 }
152 for id in cell_map.keys() {
153 if !mentioned_cells.contains(id) {
154 return Err(Error::malformed_control(format!(
155 "unmentioned ref cell: {id}"
156 ))
157 .with_pos(invoke.get_attributes()));
158 }
159 }
160 Ok(())
161}
162
163impl ConstructVisitor for WellFormed {
164 fn from(ctx: &ir::Context) -> CalyxResult<Self>
165 where
166 Self: Sized,
167 {
168 let reserved_names =
169 RESERVED_NAMES.iter().map(|s| ir::Id::from(*s)).collect();
170
171 let mut ref_cells = HashMap::new();
172 for comp in ctx.components.iter() {
173 let cellmap: LinkedHashMap<ir::Id, Cell> = comp
175 .cells
176 .iter()
177 .filter_map(|cr| {
178 let cell = cr.borrow();
179 if cell.attributes.has(ir::BoolAttr::External)
181 && comp.name != ctx.entrypoint
182 {
183 Some(Err(Error::malformed_structure("Cell cannot be marked `@external` in non-entrypoint component").with_pos(&cell.attributes)))
184 } else if cell.is_reference() {
185 Some(Ok((cell.name(), cell.clone())))
186 } else {
187 None
188 }
189 })
190 .collect::<CalyxResult<_>>()?;
191 ref_cells.insert(comp.name, cellmap);
192 }
193
194 let w_f = WellFormed {
195 reserved_names,
196 used_groups: HashSet::new(),
197 used_comb_groups: HashSet::new(),
198 ref_cells,
199 active_comb: ActiveAssignments::default(),
200 has_done_hole: HashSet::new(),
201 diag: DiagnosticContext::default(),
202 };
203
204 Ok(w_f)
205 }
206
207 fn clear_data(&mut self) {
208 self.used_groups = HashSet::default();
209 self.used_comb_groups = HashSet::default();
210 }
211}
212
213impl Named for WellFormed {
214 fn name() -> &'static str {
215 "well-formed"
216 }
217
218 fn description() -> &'static str {
219 "Check if the structure and control are well formed."
220 }
221}
222
223impl DiagnosticPass for WellFormed {
224 fn diagnostics(&self) -> &DiagnosticContext {
225 &self.diag
226 }
227}
228
229fn obvious_conflicts<'a, I1, I2>(assigns1: I1, assigns2: I2) -> CalyxResult<()>
234where
235 I1: Iterator<Item = &'a ir::Assignment<Nothing>>,
236 I2: Iterator<Item = &'a ir::Assignment<StaticTiming>>,
237{
238 let dsts1 = assigns1
239 .filter(|a| a.guard.is_true())
240 .map(|a| (a.dst.borrow().canonical(), a.attributes.copy_span()));
241 let dsts2 = assigns2
242 .filter(|a| a.guard.is_true())
243 .map(|a| (a.dst.borrow().canonical(), a.attributes.copy_span()));
244 let dsts = dsts1.chain(dsts2);
245 let dst_grps = dsts
246 .sorted_by(|(dst1, _), (dst2, _)| ir::Canonical::cmp(dst1, dst2))
247 .group_by(|(dst, _)| dst.clone());
248
249 for (_, group) in &dst_grps {
250 let assigns = group.collect_vec();
251 if assigns.len() > 1 {
252 let mut asgn_iter = assigns.into_iter().rev();
253 return Err(Error::malformed_structure(
254 "Obviously conflicting assignments found",
255 )
256 .with_pos(&asgn_iter.next().unwrap().1)
257 .with_annotations(asgn_iter.map(|(cannon, pos)| {
258 (pos, format!("`{cannon}` is also written to here"))
259 })));
260 }
261 }
262 Ok(())
263}
264
265fn subtype(cell_out: &Cell, cell_in: &Cell) -> bool {
271 for port in cell_out.ports() {
272 match cell_in.find(port.borrow().name) {
273 Some(port_in) => {
274 if !port.borrow().type_equivalent(&port_in.borrow()) {
275 return false;
276 }
277 }
278 None => {
279 return false;
280 }
281 }
282 }
283 true
284}
285
286fn check_fast_seq_invariant(seq: &Seq) -> CalyxResult<()> {
289 if seq.stmts.is_empty() {
290 return Ok(());
291 }
292 let mut last_is_static = seq
293 .stmts
294 .first()
295 .expect("non-empty already asserted")
296 .is_static();
297 for stmt in seq.stmts.iter().skip(1) {
298 if stmt.is_static() == last_is_static {
299 return Err(Error::malformed_control(
300 "`seq` marked `@fast` does not contain alternating static-dynamic control children (see #1828)",
301 ));
302 }
303 last_is_static = stmt.is_static();
304 }
305 Ok(())
306}
307
308impl Visitor for WellFormed {
309 fn start(
310 &mut self,
311 comp: &mut Component,
312 _ctx: &LibrarySignatures,
313 comps: &[ir::Component],
314 ) -> VisResult {
315 for cell_ref in comp.cells.iter() {
316 let cell = cell_ref.borrow();
317 if self.reserved_names.contains(&cell.name()) {
319 self.diag.err(
320 Error::reserved_name(cell.name())
321 .with_pos(cell.get_attributes()),
322 );
323 }
324 if cell.is_reference() {
326 if cell.is_primitive(Some("std_const")) {
327 self.diag.err(
328 Error::malformed_structure(
329 "constant not allowed for ref cells".to_string(),
330 )
331 .with_pos(cell.get_attributes()),
332 );
333 }
334 if matches!(cell.prototype, CellType::ThisComponent) {
335 unreachable!(
336 "the current component not allowed for ref cells"
337 );
338 }
339 }
340 }
341
342 if comp.is_comb {
345 if !matches!(&*comp.control.borrow(), ir::Control::Empty(..)) {
346 self.diag.err(Error::malformed_structure(format!("Component `{}` is marked combinational but has a non-empty control program", comp.name)));
347 }
348
349 if !comp.get_groups().is_empty() {
350 let group = comp.get_groups().iter().next().unwrap().borrow();
351 self.diag.err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
352 }
353
354 if !comp.get_static_groups().is_empty() {
355 let group =
356 comp.get_static_groups().iter().next().unwrap().borrow();
357 self.diag.err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
358 }
359
360 if !comp.comb_groups.is_empty() {
361 let group = comp.comb_groups.iter().next().unwrap().borrow();
362 self.diag.err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains a group `{}`", comp.name, group.name())).with_pos(&group.attributes));
363 }
364
365 for cell_ref in comp.cells.iter() {
366 let cell = cell_ref.borrow();
367 let is_comb = match &cell.prototype {
368 CellType::Primitive { is_comb, .. } => is_comb.to_owned(),
369 CellType::Constant { .. } => true,
370 CellType::Component { name } => {
371 let comp_idx =
372 comps.iter().position(|x| x.name == name).unwrap();
373 let comp = comps
374 .get(comp_idx)
375 .expect("Found cell that does not exist");
376 comp.is_comb
377 }
378 _ => false,
379 };
380 if !is_comb {
381 self.diag.err(Error::malformed_structure(format!("Component `{}` is marked combinational but contains non-combinational cell `{}`", comp.name, cell.name())).with_pos(&cell.attributes));
382 }
383 }
384 }
385 if comp.latency.is_some() {
387 assert!(
388 matches!(&*comp.control.borrow(), &ir::Control::Static(_)),
389 "static component {} does not have static control. This should have been checked in ast_to_ir",
390 comp.name
391 );
392 }
393
394 let comp_sig = &comp.signature.borrow();
400 let go_ports =
401 comp_sig.find_all_with_attr(ir::NumAttr::Go).collect_vec();
402 if go_ports.iter().any(|go_port| {
403 go_port.borrow().attributes.has(ir::NumAttr::Interval)
404 }) {
405 match &*comp.control.borrow() {
406 ir::Control::Static(_) | ir::Control::Empty(_) => (),
407 _ => return self.diag.early_return_err(Error::malformed_structure(
408 format!("component {} has dynamic control but has @interval annotations", comp.name),
409 )
410 .with_pos(&comp.attributes)),
411 };
412 if !comp.control.borrow().is_empty() {
413 let reference_val = match go_ports[0]
416 .borrow()
417 .attributes
418 .get(ir::NumAttr::Interval)
419 {
420 Some(val) => val,
421 None => {
422 return self.diag.early_return_err(Error::malformed_structure(
423 "@interval(n) attribute on all @go ports since there is static<n> control",
424 )
425 .with_pos(&comp.attributes));
426 }
427 };
428 for go_port in &go_ports {
430 let go_port_val = match go_port
431 .borrow()
432 .attributes
433 .get(ir::NumAttr::Interval)
434 {
435 Some(val) => val,
436 None => {
437 self.diag.err(Error::malformed_structure(format!(
438 "@go port expected @interval({reference_val}) attribute on all ports \
439 since the component has static<n> control",
440 ))
441 .with_pos(&comp.attributes));
442 continue;
443 }
444 };
445 if go_port_val != reference_val {
446 self.diag.err(Error::malformed_structure(format!(
447 "@go port expected @interval {reference_val}, got @interval {go_port_val}",
448 ))
449 .with_pos(&go_port.borrow().attributes));
450 continue;
451 }
452 match comp.control.borrow().get_latency() {
454 None => {
455 unreachable!("already checked control is static")
456 }
457 Some(control_latency) => {
458 if control_latency != reference_val {
459 self.diag.err(Error::malformed_structure(format!(
460 "component {} expected @interval {reference_val}, got @interval {control_latency}", comp.name,
461 ))
462 .with_pos(&comp.attributes));
463 }
464 }
465 }
466 }
467 }
468 }
469
470 for gr in comp.get_groups().iter() {
473 let group = gr.borrow();
474 let gname = group.name();
475 let mut has_done = false;
476 for assign in &group.assignments {
478 let dst = assign.dst.borrow();
479 if port_is_static_prim(&dst) {
480 self.diag.err(
481 Error::malformed_structure(format!(
482 "Static cell `{}` written to in non-static group",
483 dst.get_parent_name()
484 ))
485 .with_pos(&assign.attributes),
486 );
487 }
488 if dst.is_hole() && dst.name == "done" {
489 if has_done {
491 self.diag.err(
492 Error::malformed_structure(format!(
493 "Group `{gname}` has multiple done conditions"
494 ))
495 .with_pos(&assign.attributes),
496 );
497 } else {
498 has_done = true;
499 }
500 if gname != dst.get_parent_name() {
502 self.diag.err(Error::malformed_structure(
503 format!(
504 "Group `{}` refers to the done condition of another group (`{}`)",
505 gname,
506 dst.get_parent_name(),
507 )).with_pos(&assign.attributes));
508 }
509 }
510 }
511
512 if has_done {
515 self.has_done_hole.insert(gname);
516 } else {
517 self.diag.err(
518 Error::malformed_structure(format!(
519 "No writes to the `done' hole for group `{gname}'",
520 ))
521 .with_pos(&group.attributes),
522 );
523 }
524 }
525
526 for gr in comp.get_static_groups().iter() {
530 let group = gr.borrow();
531 let group_latency = group.get_latency();
532 for assign in &group.assignments {
534 assign.guard.check_for_each_info(
535 &mut |static_timing: &StaticTiming| {
536 if static_timing.get_interval().0
537 >= static_timing.get_interval().1
538 {
539 Err(Error::malformed_structure(format!(
540 "Static Timing Guard has improper interval: `{static_timing}`"
541 ))
542 .with_pos(&assign.attributes))
543 } else if static_timing.get_interval().1 > group_latency {
544 Err(Error::malformed_structure(format!(
545 "Static Timing Guard has interval `{static_timing}`, which is out of bounds since its static group has latency {group_latency}"
546 ))
547 .with_pos(&assign.attributes))
548 } else {
549 Ok(())
550 }
551 },
552 )?;
553 }
554 }
555
556 obvious_conflicts(
558 comp.continuous_assignments.iter(),
559 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
560 )
561 .accumulate_err(&mut self.diag)?;
562 for cgr in comp.comb_groups.iter() {
564 for assign in &cgr.borrow().assignments {
565 let dst = assign.dst.borrow();
566 if port_is_static_prim(&dst) {
567 self.diag.err(
568 Error::malformed_structure(format!(
569 "Static cell `{}` written to in non-static group",
570 dst.get_parent_name()
571 ))
572 .with_pos(&assign.attributes),
573 );
574 }
575 }
576 obvious_conflicts(
577 cgr.borrow()
578 .assignments
579 .iter()
580 .chain(comp.continuous_assignments.iter()),
581 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
582 )
583 .accumulate_err(&mut self.diag)?;
584 }
585
586 Ok(Action::Continue)
587 }
588
589 fn static_enable(
590 &mut self,
591 s: &mut ir::StaticEnable,
592 comp: &mut Component,
593 _ctx: &LibrarySignatures,
594 _comps: &[ir::Component],
595 ) -> VisResult {
596 self.used_groups.insert(s.group.borrow().name());
597
598 let group = s.group.borrow();
599
600 obvious_conflicts(
602 comp.continuous_assignments
603 .iter()
604 .chain(self.active_comb.iter()),
605 group.assignments.iter(),
606 )
607 .map_err(|err| {
608 err.with_annotation(
609 &s.attributes,
610 "Assignments activated by group static enable, causing the conflict",
611 )
612 })
613 .accumulate_err(&mut self.diag)?;
614
615 Ok(Action::Continue)
616 }
617
618 fn enable(
619 &mut self,
620 s: &mut ir::Enable,
621 comp: &mut Component,
622 _ctx: &LibrarySignatures,
623 _comps: &[ir::Component],
624 ) -> VisResult {
625 let group = s.group.borrow();
626 let gname = group.name();
627 self.used_groups.insert(gname);
628
629 if !self.has_done_hole.contains(&gname) {
632 return Ok(Action::Continue);
633 }
634
635 let asgn = group.done_cond();
636 let const_done_assign =
637 asgn.guard.is_true() && asgn.src.borrow().is_constant(1, 1);
638
639 if const_done_assign {
640 self.diag.err(Error::malformed_structure("Group with constant done condition is invalid. Use `comb group` instead to define a combinational group.").with_pos(&group.attributes));
641 }
642
643 if group
645 .attributes
646 .get(ir::NumAttr::Promotable)
647 .map(|v| v == 0)
648 .unwrap_or(false)
649 {
650 self.diag.err(Error::malformed_structure("Group with annotation \"promotable\"=0 is invalid. Use `comb group` instead to define a combinational group or if the group's done condition is not constant, provide the correct \"static\" annotation.").with_pos(&group.attributes));
651 }
652
653 obvious_conflicts(
655 group
656 .assignments
657 .iter()
658 .chain(comp.continuous_assignments.iter())
659 .chain(self.active_comb.iter()),
660 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
661 )
662 .map_err(|err| {
663 err.with_annotation(
664 &s.attributes,
665 "Assignments activated by group enable, causing the conflict",
666 )
667 })
668 .accumulate_err(&mut self.diag)?;
669
670 Ok(Action::Continue)
671 }
672
673 fn invoke(
674 &mut self,
675 s: &mut ir::Invoke,
676 _comp: &mut Component,
677 _ctx: &LibrarySignatures,
678 _comps: &[ir::Component],
679 ) -> VisResult {
680 if let Some(c) = &s.comb_group {
681 self.used_comb_groups.insert(c.borrow().name());
682 }
683 let cell = s.comp.borrow();
685
686 if let CellType::Component { name: id } = &cell.prototype {
687 require_subtype(Invoke::Invoke(s), &self.ref_cells, id)?;
688 }
689 Ok(Action::Continue)
690 }
691
692 fn static_invoke(
693 &mut self,
694 s: &mut ir::StaticInvoke,
695 _comp: &mut Component,
696 _ctx: &LibrarySignatures,
697 _comps: &[ir::Component],
698 ) -> VisResult {
699 let cell = s.comp.borrow();
701
702 if let CellType::Component { name: id } = &cell.prototype {
703 require_subtype(Invoke::StaticInvoke(s), &self.ref_cells, id)?;
704 }
705 Ok(Action::Continue)
706 }
707
708 fn start_if(
709 &mut self,
710 s: &mut ir::If,
711 _comp: &mut Component,
712 _sigs: &LibrarySignatures,
713 _comps: &[ir::Component],
714 ) -> VisResult {
715 if let Some(cgr) = &s.cond {
716 let cg = cgr.borrow();
717 let assigns = &cg.assignments;
718 obvious_conflicts(
720 assigns.iter().chain(self.active_comb.iter()),
721 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
722 )
723 .map_err(|err| {
724 err.with_annotation(
725 &s.attributes,
726 format!(
727 "Assignments from `{}' are activated here, causing the conflict",
728 cg.name()
729 ),
730 )
731 })
732 .accumulate_err(&mut self.diag)?;
733 self.active_comb.push(assigns);
735 } else if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
736 let err = Error::misc(format!(
737 "If statement has no comb group and its condition port {} is unstable",
738 s.port.borrow().canonical()
739 )).with_pos(&s.attributes);
740 self.diag.warning(err);
741 }
742 Ok(Action::Continue)
743 }
744
745 fn start_static_if(
746 &mut self,
747 s: &mut ir::StaticIf,
748 _comp: &mut Component,
749 _sigs: &LibrarySignatures,
750 _comps: &[ir::Component],
751 ) -> VisResult {
752 if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
753 let err = Error::misc(format!(
754 "static if statement's condition port {} is unstable",
755 s.port.borrow().canonical()
756 ))
757 .with_pos(&s.attributes);
758 self.diag.warning(err);
759 }
760 Ok(Action::Continue)
761 }
762
763 fn start_seq(
764 &mut self,
765 s: &mut calyx_ir::Seq,
766 _comp: &mut Component,
767 _sigs: &LibrarySignatures,
768 _comps: &[calyx_ir::Component],
769 ) -> VisResult {
770 if s.attributes.has(BoolAttr::Fast) {
771 check_fast_seq_invariant(s)?;
772 }
773
774 Ok(Action::Continue)
775 }
776
777 fn finish_if(
778 &mut self,
779 s: &mut ir::If,
780 _comp: &mut Component,
781 _ctx: &LibrarySignatures,
782 _comps: &[ir::Component],
783 ) -> VisResult {
784 if let Some(cond) = &s.cond {
786 self.used_comb_groups.insert(cond.borrow().name());
787 self.active_comb.pop();
789 }
790 Ok(Action::Continue)
791 }
792
793 fn start_while(
794 &mut self,
795 s: &mut ir::While,
796 _comp: &mut Component,
797 _sigs: &LibrarySignatures,
798 _comps: &[ir::Component],
799 ) -> VisResult {
800 if let Some(cgr) = &s.cond {
801 let cg = cgr.borrow();
802 let assigns = &cg.assignments;
803 obvious_conflicts(
805 assigns.iter().chain(self.active_comb.iter()),
806 std::iter::empty::<&ir::Assignment<StaticTiming>>(),
807 )
808 .map_err(|err| {
809 let msg = s.attributes.copy_span().format(format!(
810 "Assignments from `{}' are activated here",
811 cg.name()
812 ));
813 err.with_post_msg(Some(msg))
814 })
815 .accumulate_err(&mut self.diag)?;
816 self.active_comb.push(assigns);
818 } else if !s.port.borrow().has_attribute(ir::BoolAttr::Stable) {
819 let err = Error::misc(format!(
820 "While loop has no comb group and its condition port `{}` is unstable",
821 s.port.borrow().canonical()
822 )).with_pos(&s.attributes);
823 self.diag.warning(err);
824 }
825 Ok(Action::Continue)
826 }
827
828 fn finish_while(
829 &mut self,
830 s: &mut ir::While,
831 _comp: &mut Component,
832 _ctx: &LibrarySignatures,
833 _comps: &[ir::Component],
834 ) -> VisResult {
835 if let Some(cond) = &s.cond {
837 self.used_comb_groups.insert(cond.borrow().name());
838 self.active_comb.pop();
840 }
841 Ok(Action::Continue)
842 }
843
844 fn finish(
845 &mut self,
846 comp: &mut Component,
847 _ctx: &LibrarySignatures,
848 _comps: &[ir::Component],
849 ) -> VisResult {
850 comp.for_each_assignment(|assign| {
852 assign.for_each_port(|pr| {
853 let port = pr.borrow();
854 if port.is_hole() && port.name == "go" {
855 self.used_groups.insert(port.get_parent_name());
856 }
857 None
858 })
859 });
860 comp.for_each_static_assignment(|assign| {
861 assign.for_each_port(|pr| {
862 let port = pr.borrow();
863 if port.is_hole() && port.name == "go" {
864 self.used_groups.insert(port.get_parent_name());
865 }
866 None
867 })
868 });
869
870 let mut all_groups: HashSet<ir::Id> = comp
872 .get_groups()
873 .iter()
874 .map(|g| g.borrow().name())
875 .collect();
876 let static_groups: HashSet<ir::Id> = comp
877 .get_static_groups()
878 .iter()
879 .map(|g| g.borrow().name())
880 .collect();
881 all_groups.extend(static_groups);
882
883 if let Some(group) = all_groups.difference(&self.used_groups).next() {
884 match comp.find_group(*group) {
885 Some(gr) => {
886 let gr = gr.borrow();
887 self.diag.err(
888 Error::unused(*group, "group").with_pos(&gr.attributes),
889 );
890 }
891 None => {
892 let gr = comp.find_static_group(*group).unwrap();
893 let gr = gr.borrow();
894 self.diag.err(
895 Error::unused(*group, "group").with_pos(&gr.attributes),
896 );
897 }
898 }
899 };
900
901 let all_comb_groups: HashSet<ir::Id> =
902 comp.comb_groups.iter().map(|g| g.borrow().name()).collect();
903 if let Some(comb_group) =
904 all_comb_groups.difference(&self.used_comb_groups).next()
905 {
906 let cgr = comp.find_comb_group(*comb_group).unwrap();
907 let cgr = cgr.borrow();
908 self.diag.err(
909 Error::unused(*comb_group, "combinational group")
910 .with_pos(&cgr.attributes),
911 );
912 }
913 Ok(Action::Continue)
914 }
915}