Skip to main content

veryl_analyzer/ir/
module.rs

1use crate::BigUint;
2use crate::HashMap;
3use crate::attribute::{AllowItem, Attribute};
4use crate::attribute_table;
5use crate::conv::Context;
6use crate::ir::assign_table::{AssignTable, ReferencedEntry};
7use crate::ir::{
8    Declaration, FfTable, Function, Signature, Type, VarId, VarIndex, VarKind, VarPath, Variable,
9};
10use crate::symbol::ClockDomain;
11use crate::value::ValueBigUint;
12use indent::indent_all_by;
13use std::fmt;
14use veryl_parser::resource_table::StrId;
15use veryl_parser::token_range::TokenRange;
16
17/// Per-variable list of assignment tokens, used by the combinational-loop
18/// detector to point error reports at the assign statements involved in a
19/// cycle (rather than at the variable declaration).
20pub type AssignTokens = HashMap<VarId, Vec<TokenRange>>;
21
22#[derive(Clone)]
23pub struct Module {
24    pub name: StrId,
25    /// Fully specialized identity of this IR body. Generic arguments and
26    /// connected modport-interface signatures distinguish monomorphizations.
27    pub signature: Signature,
28    pub token: TokenRange,
29    pub ports: HashMap<VarPath, VarId>,
30    pub port_types: HashMap<VarPath, (Type, ClockDomain)>,
31    pub variables: HashMap<VarId, Variable>,
32    pub functions: HashMap<VarId, Function>,
33    /// Interface members visible through a modport only because an imported
34    /// function captures them. They have paths and IDs for dependency
35    /// analysis, but are not ordinary module variables or signal ports.
36    pub interface_members: HashMap<VarId, Variable>,
37    pub declarations: Vec<Declaration>,
38    pub suppress_unassigned: bool,
39    /// Per-declaration `AssignTable.refernced` snapshot, captured at the end
40    /// of each `Declaration::eval_assign` for use by the combinational-loop
41    /// detector. Keyed by declaration index in `declarations`.
42    pub per_decl_refs: HashMap<usize, HashMap<VarId, ReferencedEntry>>,
43    /// Per-variable assignment-site tokens, captured from the merged
44    /// `AssignTable` during `eval_assign`. Used by the combinational-loop
45    /// detector to highlight assign statements in error reports.
46    pub assign_tokens: AssignTokens,
47    /// `FfTable` derived from this module's declarations (and updated with
48    /// `update_is_ff`). Computed during `eval_assign` so consumers
49    /// (combinational-loop detector, simulator, future analyses) can read
50    /// it as a public IR property without re-walking `gather_ff`. Consumers
51    /// that need to mutate (e.g. simulator's `force_all_ff` or post-hoist
52    /// rebuild) should clone first.
53    pub ff_table: FfTable,
54}
55
56impl Module {
57    pub fn eval_assign(&mut self, context: &mut Context) {
58        // FfTable is a derived property of the IR -- safe to (re)build
59        // even when `suppress_unassigned` short-circuits the rest of
60        // eval_assign. Consumers expect `ff_table` populated whenever
61        // the module IR is fully available.
62        self.rebuild_ff_table(context);
63
64        if self.suppress_unassigned {
65            return;
66        }
67
68        context.variables = self.variables.clone();
69        context.functions = self.functions.clone();
70
71        let mut assign_table = AssignTable::new(context);
72
73        for (i, x) in self.declarations.iter().enumerate() {
74            let mut new_table = AssignTable::new(context);
75            x.eval_assign(context, &mut new_table);
76            if matches!(x, Declaration::Initial(_)) {
77                new_table.exempt_initial_writes(context);
78            }
79            // Snapshot per-decl reference masks before `new_table` is dropped.
80            // `Declaration::eval_assign` no longer clears `refernced`, so the
81            // accumulated reads/writes are still in place here.
82            let snapshot = std::mem::take(&mut new_table.refernced);
83            self.per_decl_refs.insert(i, snapshot);
84            assign_table.merge_by_or_from(context, &mut new_table, true, true);
85        }
86
87        for x in self.functions.values() {
88            let mut new_table = AssignTable::new(context);
89            x.eval_assign(context, &mut new_table);
90            assign_table.merge_by_or(context, &mut new_table, false);
91        }
92
93        let mut variables = self.variables.clone();
94
95        for (key, entry) in &assign_table.table {
96            if let Some(variable) = variables.get_mut(key)
97                && let Some(array) = entry.array.total()
98            {
99                for i in 0..array {
100                    if let Some(x) = entry.mask.get(i) {
101                        variable.set_assigned(i, x.clone());
102                    }
103                }
104            }
105            // Capture assign tokens for combinational loop reporting.
106            self.assign_tokens.insert(*key, entry.tokens.clone());
107        }
108
109        for variable in variables.values() {
110            // inout ports are driven externally; unknown-size arrays can't be iterated.
111            let check_skip = variable.r#type.is_systemverilog()
112                || variable.r#type.total_array().unwrap_or(0) > context.config.evaluate_array_limit
113                || matches!(variable.kind, VarKind::Inout)
114                || variable.r#type.array.total().is_none();
115
116            if variable.is_assignable() && !check_skip {
117                let zero: BigUint = 0u32.into();
118                let full_mask = variable
119                    .total_width()
120                    .map(ValueBigUint::gen_mask)
121                    .unwrap_or_else(|| zero.clone());
122                let accumulated_reads = assign_table.accumulated_reads.get(&variable.id);
123
124                for index in &variable.unassigned() {
125                    let assigned_mask = variable
126                        .assigned
127                        .get(*index)
128                        .cloned()
129                        .unwrap_or_else(|| zero.clone());
130                    // BigUint has no bit-complement: full_mask ^ (full_mask & assigned).
131                    let unassigned_bits = &full_mask ^ &(&full_mask & &assigned_mask);
132                    let read_mask = accumulated_reads
133                        .and_then(|r| r.get(*index))
134                        .cloned()
135                        .unwrap_or_else(|| zero.clone());
136                    // Accept dead bits of a partially driven register; still warn
137                    // if nothing was assigned. Outputs are excluded: their bits are
138                    // read externally, so undriven bits leak X to the parent.
139                    let any_assigned = assigned_mask != zero;
140                    let any_read_unassigned = (&read_mask & &unassigned_bits) != zero;
141                    if any_assigned
142                        && !any_read_unassigned
143                        && !matches!(variable.kind, VarKind::Output)
144                        && unassigned_bits != zero
145                    {
146                        continue;
147                    }
148                    if unassigned_bits == zero {
149                        continue;
150                    }
151
152                    if !attribute_table::contains(
153                        &variable.token.beg,
154                        Attribute::Allow(AllowItem::UnassignVariable),
155                    ) {
156                        let index = VarIndex::from_index(*index, &variable.r#type.array);
157                        context.insert_error(crate::AnalyzerError::unassign_variable(
158                            &format!("{}{index}", variable.path),
159                            &variable.token,
160                        ));
161                    }
162                }
163            }
164        }
165    }
166
167    pub fn gather_ff(&self, context: &mut Context, table: &mut FfTable) {
168        for (i, x) in self.declarations.iter().enumerate() {
169            x.gather_ff(context, table, i);
170        }
171    }
172
173    /// (Re)compute `self.ff_table` from the current `declarations`.
174    /// Runs `gather_ff` over every declaration and applies
175    /// `update_is_ff` so the resulting table is ready for consumers.
176    pub fn rebuild_ff_table(&mut self, context: &mut Context) {
177        let saved_vars = std::mem::take(&mut context.variables);
178        let saved_funcs = std::mem::take(&mut context.functions);
179        context.variables = self.variables.clone();
180        context.functions = self.functions.clone();
181
182        let mut table = FfTable::default();
183        self.gather_ff(context, &mut table);
184        table.update_is_ff(&self.declarations, context);
185        self.ff_table = table;
186
187        context.variables = saved_vars;
188        context.functions = saved_funcs;
189    }
190}
191
192impl fmt::Display for Module {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        let mut ret = format!("module {} {{\n", self.name);
195
196        let mut variables: Vec<_> = self.variables.iter().collect();
197        variables.sort_by(|a, b| a.0.cmp(b.0));
198
199        let mut functions: Vec<_> = self.functions.iter().collect();
200        functions.sort_by(|a, b| a.0.cmp(b.0));
201
202        for (_, x) in variables {
203            let text = format!("{}\n", x);
204            ret.push_str(&indent_all_by(2, text));
205        }
206
207        for (_, x) in functions {
208            let text = format!("{}\n", x);
209            ret.push_str(&indent_all_by(2, text));
210        }
211
212        ret.push('\n');
213
214        for x in &self.declarations {
215            let text = format!("{}\n", x);
216            ret.push_str(&indent_all_by(2, text));
217        }
218
219        ret.push('}');
220        ret.fmt(f)
221    }
222}