Skip to main content

vj_sim/state/
event_plan.rs

1//! The design-wide event-pool plan (LRM 1800-2023 15.5, 23.6).
2//!
3//! Whether a body keeps its events in a pool ([`super::event_pool`]) is
4//! decided from the body's own code, but another instance's code can name
5//! its events: a pool-mode body that writes `-> s.ev`, `@(s.ev)` or
6//! `fire(s.ev)` reaches the owner's events through the owner's pool
7//! (`pool_of_key`), so the owner must have one. A body alone cannot know
8//! that, so [`EventPlanScope::enter`] walks the whole design once per run and
9//! records which instance definitions are forced into pool mode (one layout
10//! per deduplicated template, hence the decision is per definition).
11//!
12//! The plan is per-thread state (like the other design scopes), entered by
13//! `DesignSyntaxScope::enter`, which every pipeline entry point holds; with
14//! no scope entered nothing is forced.
15
16use std::cell::RefCell;
17use std::collections::HashSet;
18use std::rc::Rc;
19
20use sv_lang::kinds::{ExpressionKind, SymbolKind};
21use sv_lang::{Expression, KnownSystemName, Symbol};
22
23use super::event_pool::{event_count, needs_pool};
24
25#[derive(Default)]
26struct EventPlan {
27    /// Definition names forced into pool mode.
28    forced: HashSet<String>,
29    /// Some code uses another instance's event as a HANDLE (assigns, compares
30    /// or passes it): event ids are then design-wide (tagged with the pool
31    /// they belong to), see `group_mode`.
32    group: bool,
33}
34
35thread_local! {
36    static PLAN: RefCell<Rc<EventPlan>> = RefCell::new(Rc::default());
37}
38
39/// While alive, `event_pool` honors the design-wide plan of the design
40/// entered. Scopes nest; the outer plan returns on drop.
41pub struct EventPlanScope(Rc<EventPlan>);
42
43impl EventPlanScope {
44    pub fn enter(design: &sv_lang::Design) -> Self {
45        let plan = Rc::new(compute_plan(design));
46        EventPlanScope(PLAN.with(|p| std::mem::replace(&mut *p.borrow_mut(), plan)))
47    }
48}
49
50impl Drop for EventPlanScope {
51    fn drop(&mut self) {
52        let outer = std::mem::take(&mut self.0);
53        PLAN.with(|p| *p.borrow_mut() = outer);
54    }
55}
56
57/// The definition name of instance body `body`.
58fn definition_of(body: &Symbol<'_>) -> Option<String> {
59    if body.kind() != SymbolKind::InstanceBody {
60        return None;
61    }
62    body.instance_body_parent_instance()?
63        .instance_definition()
64        .map(|d| d.name().to_string())
65}
66
67/// Event ids are design-wide: every pool-mode instance can reach every
68/// other's pool and a handle carries its pool's tag (LRM 1800-2023 15.5.5:
69/// an event handle names an event wherever it lives). Off for every design
70/// that never uses another instance's event as a handle, whose code and image
71/// stay exactly as without the feature.
72pub fn group_mode() -> bool {
73    PLAN.with(|p| p.borrow().group)
74}
75
76/// The plan forces `body`'s definition into pool mode.
77pub(super) fn plan_forces_pool(body: &Symbol<'_>) -> bool {
78    PLAN.with(|p| {
79        let p = p.borrow();
80        !p.forced.is_empty() && definition_of(body).is_some_and(|d| p.forced.contains(&d))
81    })
82}
83
84/// One hierarchical event reference of a body's code: the static event
85/// variable named, and whether it is used as an event handle (assigned,
86/// compared, passed, tested: 15.5.5) rather than only triggered or waited on.
87struct EventRef<'d> {
88    var: Symbol<'d>,
89    handle: bool,
90}
91
92/// The static event variable `e` names through a hierarchical reference
93/// (`s.ev`, `s.ea[i]`), if it does.
94fn hierarchical_event_operand<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
95    let mut e = *e;
96    loop {
97        if e.kind() == ExpressionKind::Conversion
98            && let Some(inner) = e.conversion_operand()
99        {
100            e = inner;
101        } else if e.kind() == ExpressionKind::ElementSelect
102            && let Some(base) = e.select_value()
103        {
104            e = base;
105        } else {
106            break;
107        }
108    }
109    if e.kind() != ExpressionKind::HierarchicalValue {
110        return None;
111    }
112    let sym = e.referenced_symbol()?;
113    sym.value_type().as_ref().and_then(event_count)?;
114    Some(sym)
115}
116
117fn note<'d>(e: Option<Expression<'d>>, handle: bool, out: &mut Vec<EventRef<'d>>) {
118    if let Some(var) = e.as_ref().and_then(hierarchical_event_operand) {
119        out.push(EventRef { var, handle });
120    }
121}
122
123/// Every hierarchical event reference in `body`'s procedural code.
124fn hierarchical_event_refs<'d>(body: &Symbol<'d>) -> Vec<EventRef<'d>> {
125    let mut out = Vec::new();
126    for m in crate::netlist::module_items(body) {
127        let kind = m.kind();
128        if kind == SymbolKind::Variable && m.value_type().as_ref().and_then(event_count).is_some() {
129            note(m.initializer(), true, &mut out);
130        }
131        if kind != SymbolKind::Subroutine && kind != SymbolKind::ProceduralBlock {
132            continue;
133        }
134        let Some(code) = crate::process::cfg::proc_body(&m) else {
135            continue;
136        };
137        let mut stack = vec![code.as_sem_node()];
138        while let Some(n) = stack.pop() {
139            if let Some(s) = n.as_statement() {
140                note(s.condition(), true, &mut out);
141                for block in crate::process::cfg::block_scopes(&s) {
142                    for v in block.members() {
143                        if v.value_type().as_ref().and_then(event_count).is_some() {
144                            note(v.initializer(), true, &mut out);
145                        }
146                    }
147                }
148            }
149            let kids = crate::process::cfg::live_children(&n);
150            if let Some(e) = n.as_expression() {
151                note(Some(e), false, &mut out);
152                // An operand of an assignment, comparison, `!`, a Boolean
153                // conversion or a call (an event argument) is used as a
154                // handle (15.5.5).
155                // `ev.triggered` reads the event's cell, it is not a handle use.
156                let is_triggered = e.kind() == ExpressionKind::Call
157                    && e.system_subroutine()
158                        .is_some_and(|s| s.known_name_id() == KnownSystemName::Triggered);
159                if !is_triggered
160                    && matches!(
161                        e.kind(),
162                        ExpressionKind::Assignment
163                            | ExpressionKind::BinaryOp
164                            | ExpressionKind::UnaryOp
165                            | ExpressionKind::Conversion
166                            | ExpressionKind::Call
167                    )
168                {
169                    for k in &kids {
170                        note(k.as_expression(), true, &mut out);
171                    }
172                }
173            }
174            stack.extend(kids);
175        }
176    }
177    out
178}
179
180fn collect_bodies<'d>(inst: Symbol<'d>, out: &mut Vec<Symbol<'d>>) {
181    if inst.kind() == SymbolKind::InstanceArray {
182        for e in inst.instance_array_elements() {
183            collect_bodies(e, out);
184        }
185        return;
186    }
187    if inst.kind() != SymbolKind::Instance {
188        return;
189    }
190    if let Some(body) = inst.instance_body() {
191        out.push(body);
192        for m in crate::netlist::module_items(&body) {
193            collect_bodies(m, out);
194        }
195    }
196}
197
198fn compute_plan(design: &sv_lang::Design) -> EventPlan {
199    let mut bodies = Vec::new();
200    for top in design.top_instances() {
201        collect_bodies(top, &mut bodies);
202    }
203    // (referrer definition, owner definition, handle use) per cross-instance
204    // reference, and the definitions that are pool-mode on their own.
205    let mut refs: Vec<(String, String, bool)> = Vec::new();
206    let mut pooled: HashSet<String> = HashSet::new();
207    for body in &bodies {
208        let Some(def) = definition_of(body) else {
209            continue;
210        };
211        if needs_pool(body) {
212            pooled.insert(def.clone());
213        }
214        let own_path = body.hierarchical_path();
215        for r in hierarchical_event_refs(body) {
216            let Some(owner) = crate::netlist::enclosing_instance_body(&r.var) else {
217                continue;
218            };
219            if owner.hierarchical_path() == own_path {
220                continue;
221            }
222            if let Some(owner_def) = definition_of(&owner) {
223                refs.push((def.clone(), owner_def, r.handle));
224            }
225        }
226    }
227    // A handle use forces both ends; any reference from a pool-mode body
228    // forces the owner (its code cannot read a legacy cell, see module docs).
229    let mut forced: HashSet<String> = HashSet::new();
230    loop {
231        let mut changed = false;
232        for (from, owner, handle) in &refs {
233            if *handle && forced.insert(from.clone()) {
234                changed = true;
235            }
236            if (*handle || pooled.contains(from) || forced.contains(from))
237                && forced.insert(owner.clone())
238            {
239                changed = true;
240            }
241        }
242        if !changed {
243            break;
244        }
245    }
246    let group = refs.iter().any(|(_, _, handle)| *handle);
247    EventPlan { forced, group }
248}