rust_hdl_core/
check_logic_loops.rs

1use 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
142/// Check a circuit for logical loops.  Logic loops are circular
143/// dependencies in the logic that are neither simulateable nor
144/// synthesizable.  For example
145/// ```rust
146/// use rust_hdl_core::prelude::*;
147/// use rust_hdl_core::check_logic_loops::check_logic_loops;
148///
149/// #[derive(LogicBlock, Default)]
150/// struct Circle {
151///    in1: Signal<In, Bit>,
152///    loc: Signal<Local, Bit>,
153///    out: Signal<Out, Bit>,
154/// }
155///
156/// impl Logic for Circle {
157///     #[hdl_gen]
158///     fn update(&mut self) {
159///         self.loc.next = self.out.val();
160///         self.out.next = self.loc.val();  // <-- head scratcher...
161///     }
162/// }
163///
164/// let mut uut = Circle::default(); uut.connect_all();
165/// assert!(check_logic_loops(&uut).is_err());
166/// ```
167///
168pub 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}