rust_hdl_core/
check_logic_loops.rs1use crate::ast::{Verilog, VerilogExpression};
2use crate::atom::{Atom, AtomKind};
3use crate::block::Block;
4use crate::check_error::{CheckError, PathedName, PathedNameList};
5use crate::named_path::NamedPath;
6use crate::probe::Probe;
7use crate::verilog_visitor::VerilogVisitor;
8use std::collections::HashSet;
9
10#[derive(Copy, Clone, Debug, PartialEq)]
11enum Mode {
12 Ignore,
13 Read,
14 Write,
15}
16
17struct VerilogLogicLoopDetector {
18 local_vars_written: HashSet<String>,
19 mode: Mode,
20 violations: Vec<String>,
21}
22
23impl Default for VerilogLogicLoopDetector {
24 fn default() -> Self {
25 Self {
26 local_vars_written: Default::default(),
27 mode: Mode::Ignore,
28 violations: Default::default(),
29 }
30 }
31}
32
33impl VerilogVisitor for VerilogLogicLoopDetector {
34 fn visit_slice_assignment(
35 &mut self,
36 base: &VerilogExpression,
37 _width: &usize,
38 offset: &VerilogExpression,
39 replacement: &VerilogExpression,
40 ) {
41 let current_mode = self.mode;
42 self.mode = Mode::Read;
43 self.visit_expression(offset);
44 self.visit_expression(replacement);
45 self.mode = Mode::Write;
46 self.visit_expression(base);
47 self.mode = current_mode;
48 }
49
50 fn visit_signal(&mut self, c: &str) {
51 let myname = c.replace("$next", "");
52 match self.mode {
53 Mode::Ignore => {}
54 Mode::Write => {
55 self.local_vars_written.insert(myname);
56 }
57 Mode::Read => {
58 if !self.local_vars_written.contains(&myname) {
59 self.violations.push(myname);
60 }
61 }
62 }
63 }
64
65 fn visit_assignment(&mut self, l: &VerilogExpression, r: &VerilogExpression) {
66 let current_mode = self.mode;
67 self.mode = Mode::Read;
68 self.visit_expression(r);
69 self.mode = Mode::Write;
70 self.visit_expression(l);
71 self.mode = current_mode;
72 }
73}
74
75fn get_logic_loop_candidates(uut: &dyn Block) -> Vec<String> {
76 match &uut.hdl() {
77 Verilog::Combinatorial(code) => {
78 let mut det = VerilogLogicLoopDetector::default();
79 det.visit_block(code);
80 if det.violations.is_empty() {
81 vec![]
82 } else {
83 det.violations
84 }
85 }
86 _ => vec![],
87 }
88}
89
90#[derive(Default, Clone, Debug)]
91struct LocalVars {
92 path: NamedPath,
93 names: Vec<HashSet<String>>,
94 loops: PathedNameList,
95}
96
97impl LocalVars {
98 fn update_loops(&mut self, candidates: &[String]) {
99 for candidate in candidates {
100 if self.names.last().unwrap().contains(candidate) {
101 self.loops.push(PathedName {
102 path: self.path.to_string(),
103 name: candidate.to_string(),
104 })
105 }
106 }
107 }
108}
109
110impl Probe for LocalVars {
111 fn visit_start_scope(&mut self, name: &str, _node: &dyn Block) {
112 self.path.push(name);
113 self.names.push(Default::default());
114 }
115
116 fn visit_start_namespace(&mut self, name: &str, _node: &dyn Block) {
117 self.path.push(name);
118 self.names.push(Default::default());
119 }
120
121 fn visit_atom(&mut self, name: &str, signal: &dyn Atom) {
122 match signal.kind() {
123 AtomKind::LocalSignal | AtomKind::OutputParameter => {
124 self.names.last_mut().unwrap().insert(name.to_string());
125 }
126 _ => {}
127 }
128 }
129
130 fn visit_end_namespace(&mut self, _name: &str, _node: &dyn Block) {
131 self.names.pop();
132 self.path.pop();
133 }
134
135 fn visit_end_scope(&mut self, _name: &str, node: &dyn Block) {
136 self.update_loops(&get_logic_loop_candidates(node));
137 self.path.pop();
138 self.names.pop();
139 }
140}
141
142pub fn check_logic_loops(uut: &dyn Block) -> Result<(), CheckError> {
169 let mut visitor = LocalVars::default();
170 uut.accept("uut", &mut visitor);
171 if visitor.loops.is_empty() {
172 Ok(())
173 } else {
174 Err(CheckError::LogicLoops(visitor.loops))
175 }
176}