rust_hdl_core/
check_write_inputs.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 VerilogWriteCollector {
18    vars_written: HashSet<String>,
19    vars_read: HashSet<String>,
20    mode: Mode,
21}
22
23impl Default for VerilogWriteCollector {
24    fn default() -> Self {
25        Self {
26            vars_written: Default::default(),
27            vars_read: Default::default(),
28            mode: Mode::Ignore,
29        }
30    }
31}
32
33fn get_write_list(uut: &dyn Block) -> HashSet<String> {
34    match &uut.hdl() {
35        Verilog::Combinatorial(code) => {
36            let mut det = VerilogWriteCollector::default();
37            det.visit_block(code);
38            det.vars_written
39        }
40        _ => Default::default(),
41    }
42}
43
44impl VerilogVisitor for VerilogWriteCollector {
45    fn visit_slice_assignment(
46        &mut self,
47        base: &VerilogExpression,
48        _width: &usize,
49        offset: &VerilogExpression,
50        replacement: &VerilogExpression,
51    ) {
52        let current_mode = self.mode;
53        self.mode = Mode::Read;
54        self.visit_expression(offset);
55        self.visit_expression(replacement);
56        self.mode = Mode::Write;
57        self.visit_expression(base);
58        self.mode = current_mode;
59    }
60
61    fn visit_signal(&mut self, c: &str) {
62        let myname = c.replace("$next", "");
63        match self.mode {
64            Mode::Ignore => {}
65            Mode::Write => {
66                self.vars_written.insert(myname);
67            }
68            Mode::Read => {
69                self.vars_read.insert(myname);
70            }
71        }
72    }
73
74    fn visit_assignment(&mut self, l: &VerilogExpression, r: &VerilogExpression) {
75        let current_mode = self.mode;
76        self.mode = Mode::Read;
77        self.visit_expression(r);
78        self.mode = Mode::Write;
79        self.visit_expression(l);
80        self.mode = current_mode;
81    }
82}
83
84#[derive(Default)]
85struct CheckInputsNotDriven {
86    path: NamedPath,
87    namespace: NamedPath,
88    input_parameters: Vec<Vec<String>>,
89    failures: PathedNameList,
90}
91
92impl Probe for CheckInputsNotDriven {
93    fn visit_start_scope(&mut self, name: &str, _node: &dyn Block) {
94        self.input_parameters.push(vec![]);
95        self.path.push(name);
96        self.namespace.reset();
97    }
98
99    fn visit_start_namespace(&mut self, name: &str, _node: &dyn Block) {
100        self.namespace.push(name);
101    }
102
103    fn visit_atom(&mut self, name: &str, signal: &dyn Atom) {
104        let namespace = self.namespace.flat("$");
105        let name = if namespace.is_empty() {
106            name.to_owned()
107        } else {
108            format!("{}${}", namespace, name)
109        };
110        if let AtomKind::InputParameter = signal.kind() {
111            self.input_parameters.last_mut().unwrap().push(name);
112        }
113    }
114
115    fn visit_end_namespace(&mut self, _name: &str, _node: &dyn Block) {
116        self.namespace.pop();
117    }
118
119    fn visit_end_scope(&mut self, _name: &str, node: &dyn Block) {
120        let written = get_write_list(node);
121        let my_params = self.input_parameters.last().unwrap();
122        for param in my_params {
123            if written.contains(param) {
124                self.failures.push(PathedName {
125                    path: self.path.to_string(),
126                    name: param.to_owned(),
127                })
128            }
129        }
130        self.path.pop();
131        self.input_parameters.pop();
132    }
133}
134
135/// Check a circuit to make sure that `Signal`s of type `In` are
136/// not written by the HDL kernel.  In RustHDL, you are not allowed
137/// to write to input signals from within a module.
138/// ```rust
139/// use rust_hdl_core::prelude::*;
140/// use rust_hdl_core::check_write_inputs::check_inputs_not_written;
141///
142/// #[derive(LogicBlock, Default)]
143/// struct BadGuy {
144///    pub in1: Signal<In, Bit>,
145/// }
146///
147/// impl Logic for BadGuy {
148///    #[hdl_gen]
149///    fn update(&mut self) {
150///       self.in1.next = false; // <-- rustc is OK with this, but RustHDL is not.
151///    }
152/// }
153///
154/// let mut uut = BadGuy::default(); uut.connect_all();
155/// assert!(check_inputs_not_written(&uut).is_err());
156/// ```
157pub fn check_inputs_not_written(uut: &dyn Block) -> Result<(), CheckError> {
158    let mut visitor = CheckInputsNotDriven::default();
159    uut.accept("uut", &mut visitor);
160    if visitor.failures.is_empty() {
161        Ok(())
162    } else {
163        Err(CheckError::WritesToInputs(visitor.failures))
164    }
165}