Skip to main content

veryl_analyzer/ir/
ff_table.rs

1use crate::HashMap;
2use crate::ir::VarId;
3
4/// Identifies the LHS of an assignment: (VarId, Option<array_element_index>).
5/// None index means dynamic (unknown at analysis time).
6type AssignTarget = (VarId, Option<usize>);
7
8#[derive(Clone, Debug)]
9pub struct FfTableEntry {
10    pub assigned: Option<usize>,
11    /// (decl_index, assign_target, from_ff) pairs where this variable is referenced.
12    /// None assign_target for condition expressions (if/case).
13    /// from_ff: true if the reference is in an always_ff block (NBA-sensitive),
14    /// false if in always_comb / continuous assign (re-evaluated after NBA).
15    pub refered: Vec<(usize, Option<AssignTarget>, bool)>,
16    pub is_ff: bool,
17    pub assigned_comb: Option<usize>,
18}
19
20impl FfTableEntry {
21    fn update_is_ff(&mut self, self_key: (VarId, usize)) {
22        if let Some(assigned_decl) = self.assigned {
23            // FF classification rules (strict NBA semantics):
24            // - A variable may be treated as comb (ff_opt) only if no always_ff
25            //   block reads it (cross-block NBA races would be violated).
26            // - always_comb / continuous assigns re-evaluate after NBA in SV,
27            //   so they correctly see new FF values; ff_opt is safe for them.
28            // - Within the same always_ff (assigned_decl), self-reference is
29            //   safe; but cross-variable assignments must see old values.
30            self.is_ff = self.refered.iter().any(|(decl, assign_target, from_ff)| {
31                if !from_ff {
32                    return false;
33                }
34                if *decl != assigned_decl {
35                    return true;
36                }
37                match assign_target {
38                    Some((target_id, target_idx)) => {
39                        if *target_id != self_key.0 {
40                            return true;
41                        }
42                        // Same VarId: compare array index.
43                        // None index (dynamic) is conservative → FF.
44                        match target_idx {
45                            Some(idx) => *idx != self_key.1,
46                            None => true,
47                        }
48                    }
49                    None => true,
50                }
51            });
52        }
53    }
54}
55
56#[derive(Clone, Debug, Default)]
57pub struct FfTable {
58    pub table: HashMap<(VarId, usize), FfTableEntry>,
59}
60
61impl FfTable {
62    pub fn update_is_ff(&mut self) {
63        let keys: Vec<_> = self.table.keys().cloned().collect();
64        for key in keys {
65            self.table.get_mut(&key).unwrap().update_is_ff(key);
66        }
67    }
68
69    /// Force all always_ff-assigned variables to FF, disabling the
70    /// assign_target refinement. Used by --disable-ff-opt for debugging.
71    pub fn force_all_ff(&mut self) {
72        for entry in self.table.values_mut() {
73            if entry.assigned.is_some() {
74                entry.is_ff = true;
75            }
76        }
77    }
78
79    pub fn is_ff(&self, id: VarId, index: usize) -> bool {
80        if let Some(x) = self.table.get(&(id, index)) {
81            x.is_ff
82        } else {
83            false
84        }
85    }
86
87    pub fn insert_refered(
88        &mut self,
89        id: VarId,
90        index: usize,
91        decl: usize,
92        assign_target: Option<AssignTarget>,
93        from_ff: bool,
94    ) {
95        self.table
96            .entry((id, index))
97            .and_modify(|x| x.refered.push((decl, assign_target, from_ff)))
98            .or_insert(FfTableEntry {
99                assigned: None,
100                refered: vec![(decl, assign_target, from_ff)],
101                is_ff: false,
102                assigned_comb: None,
103            });
104    }
105
106    pub fn insert_assigned(&mut self, id: VarId, index: usize, decl: usize) {
107        self.table
108            .entry((id, index))
109            .and_modify(|x| x.assigned = Some(decl))
110            .or_insert(FfTableEntry {
111                assigned: Some(decl),
112                refered: vec![],
113                is_ff: false,
114                assigned_comb: None,
115            });
116    }
117
118    pub fn insert_assigned_comb(&mut self, id: VarId, index: usize, decl: usize) {
119        self.table
120            .entry((id, index))
121            .and_modify(|x| x.assigned_comb = Some(decl))
122            .or_insert(FfTableEntry {
123                assigned: None,
124                refered: vec![],
125                is_ff: false,
126                assigned_comb: Some(decl),
127            });
128    }
129
130    #[cfg(debug_assertions)]
131    pub fn validate(&self) {
132        for ((id, index), entry) in &self.table {
133            if let (Some(ff_decl), Some(comb_decl)) = (entry.assigned, entry.assigned_comb) {
134                log::warn!(
135                    "FfTable: variable {:?}[{}] assigned in both always_ff (decl {}) and always_comb (decl {})",
136                    id,
137                    index,
138                    ff_decl,
139                    comb_decl
140                );
141            }
142        }
143    }
144}