vj_sim/state/event_pool.rs
1//! The event pool: event handles (LRM 1800-2023 15.5.2 - 15.5.5).
2//!
3//! A named event is an object; an `event` variable holds a handle to one. The
4//! handle can be re-pointed by assignment (`e1 = e2` merges: both then name
5//! the same object), cleared (`e = null`), compared (`e1 == e2`, `e == null`),
6//! passed to a task, and an automatic `event` (a local of an automatic block
7//! or task, an automatic task's formal, a local `event` array) is a new
8//! object each time it is declared.
9//!
10//! None of that fits an event being one fixed STATE cell, so an instance body
11//! that uses any of it ([`event_pool`]) keeps all its events in one hidden
12//! memory of 128-bit event cells -- the layout of an event array's rows
13//! (`state::EVENT_CELL_WIDTH`) -- addressed by an object id, the row number:
14//!
15//! - row 0 holds the dynamic allocation counter; id 0 is `null`;
16//! - rows `1..=S` are the body's static event variables (`S` =
17//! [`EventPool::static_rows`]), an array's elements consecutive;
18//! - rows `S+1..=2S` are the *handle cells* of those, row `S + id`: a static
19//! event variable's own handle, 0 until it is assigned (it names itself),
20//! then the target's id (all ones: `null`);
21//! - the rest ([`EventPool::dynamic_base`] on) are the objects the automatic
22//! events of the running processes are allocated, one per declaration
23//! executed ([`DYNAMIC_ROWS`] in all).
24//!
25//! An automatic event variable is a 64-bit object id in the process frame.
26//! A wait, a trigger, `.triggered` and `->>` all resolve the event expression
27//! to an object id and act on that row (the per-element machinery of an
28//! event array's rows).
29//!
30//! The pool's legacy per-variable cells are still laid out (so every other
31//! consumer of the signal map keeps working), but a body in pool mode reads
32//! and writes its events only through the pool: a hierarchical reference
33//! from ANOTHER instance's code to such an event does not see the pool.
34
35use sv_lang::kinds::{ExpressionKind, SymbolKind};
36use sv_lang::{Symbol, Type};
37
38use crate::symid::SymbolId;
39
40/// Rows of the pool for the automatic events allocated at run time. An
41/// allocation past them is a loud runtime error, never a reused object.
42pub const DYNAMIC_ROWS: u32 = 1 << 14;
43
44/// One static event variable (or array of them) of the pool.
45#[derive(Clone, Copy)]
46pub struct PoolStatic<'d> {
47 pub var: Symbol<'d>,
48 /// The object id of its (first) event.
49 pub first: u32,
50 /// How many events: 1, or an array's element count.
51 pub count: u32,
52}
53
54/// An instance body's event pool.
55pub struct EventPool<'d> {
56 pub statics: Vec<PoolStatic<'d>>,
57 /// `S`: the number of static event objects.
58 pub static_rows: u32,
59}
60
61impl EventPool<'_> {
62 /// Rows of the pool memory.
63 pub fn total_rows(&self) -> u32 {
64 self.dynamic_base() + DYNAMIC_ROWS
65 }
66
67 /// The first dynamic object id.
68 pub fn dynamic_base(&self) -> u32 {
69 2 * self.static_rows + 1
70 }
71}
72
73/// The signal-map key of the pool memory itself.
74pub fn pool_key() -> SymbolId {
75 SymbolId::synthetic_tagged("event_pool")
76}
77
78/// The signal-map key of peer `j`'s pool (group mode, see
79/// `event_plan::group_mode`): the pool memory of the `j`-th pool-mode instance,
80/// as that instance's tag `j + 1` names it. Registered in EVERY pool-mode
81/// instance (its own pool under its own tag too).
82pub fn peer_key(j: u32) -> SymbolId {
83 SymbolId::synthetic_tagged(&format!("event_pool_peer{j}"))
84}
85
86/// The signal-map key of the pool entry of static event variable `var`: a
87/// memory entry sharing the pool's slot, `num_rows` its event count and
88/// `lower_index` the id of its first event.
89pub fn static_key(var: &SymbolId) -> SymbolId {
90 var.derive("event_pool_static")
91}
92
93/// The signal-map key of the pool entry as seen from static event variable
94/// `var`: the pool's memory (`lower_index` = `S`, the static count) under a
95/// key unique to the variable, so another instance that names `var`
96/// hierarchically (`s.ev`) can register the pool of the instance that owns it
97/// beside [`static_key`] (the pool's own key, [`pool_key`], is the same in
98/// every body).
99pub fn pool_of_key(var: &SymbolId) -> SymbolId {
100 var.derive("event_pool_of")
101}
102
103/// The number of events `ty` holds when it is an event or a fixed-size
104/// array of events; `None` for any other type.
105pub fn event_count(ty: &Type<'_>) -> Option<u32> {
106 let mut rows: u32 = 1;
107 let mut cur = *ty;
108 while cur.is_unpacked_array() && !super::storage::is_collection(&cur) {
109 rows = rows.checked_mul(cur.fixed_unpacked_array_range().width())?;
110 cur = cur.element_type()?;
111 }
112 cur.is_event().then_some(rows)
113}
114
115/// The pool of `body`'s events, when its code needs one: it declares an
116/// event formal or an automatic event (variable or array), assigns an event
117/// or compares one.
118pub fn event_pool<'d>(body: &Symbol<'d>) -> Option<EventPool<'d>> {
119 if !needs_pool(body) && !super::event_plan::plan_forces_pool(body) {
120 return None;
121 }
122 let mut statics = Vec::new();
123 let mut next: u32 = 1;
124 for v in crate::netlist::state_items(body) {
125 if !matches!(v.kind(), SymbolKind::Variable | SymbolKind::FormalArgument) {
126 continue;
127 }
128 let Some(count) = v.value_type().as_ref().and_then(event_count) else {
129 continue;
130 };
131 statics.push(PoolStatic {
132 var: v,
133 first: next,
134 count,
135 });
136 next += count;
137 }
138 Some(EventPool {
139 statics,
140 static_rows: next - 1,
141 })
142}
143
144/// The static event variable that event expression `e` names through a
145/// hierarchical reference (`s.ev`, `s.ea[i]`) when its instance keeps its
146/// events in a pool ([`event_pool`]); `None` for anything else.
147///
148/// A pool-mode body reads and writes its events only through the pool (module
149/// docs), so another instance's trigger, `.triggered` or wait on the legacy
150/// cell would be silently lost: the callers refuse the construct by name.
151pub fn hierarchical_pool_event<'d>(e: &sv_lang::Expression<'d>) -> Option<Symbol<'d>> {
152 let mut e = *e;
153 loop {
154 if e.kind() == ExpressionKind::Conversion
155 && let Some(inner) = e.conversion_operand()
156 {
157 e = inner;
158 } else if e.kind() == ExpressionKind::ElementSelect
159 && let Some(base) = e.select_value()
160 {
161 e = base;
162 } else {
163 break;
164 }
165 }
166 if e.kind() != ExpressionKind::HierarchicalValue {
167 return None;
168 }
169 let sym = e.referenced_symbol()?;
170 if !sym.value_type().as_ref().and_then(event_count).is_some() {
171 return None;
172 }
173 let owner = crate::netlist::enclosing_instance_body(&sym)?;
174 event_pool(&owner).is_some().then_some(sym)
175}
176
177pub(super) fn needs_pool(body: &Symbol<'_>) -> bool {
178 let automatic_event = |v: &Symbol<'_>| {
179 matches!(v.kind(), SymbolKind::Variable | SymbolKind::FormalArgument)
180 && v.value_type().as_ref().and_then(event_count).is_some()
181 && (v.kind() == SymbolKind::FormalArgument
182 || v.variable_lifetime() != sv_lang::VariableLifetime::Static)
183 };
184 for m in crate::netlist::module_items(body) {
185 let kind = m.kind();
186 if kind == SymbolKind::Subroutine && m.members().any(|v| automatic_event(&v)) {
187 return true;
188 }
189 if kind != SymbolKind::Subroutine && kind != SymbolKind::ProceduralBlock {
190 continue;
191 }
192 let Some(code) = crate::process::cfg::proc_body(&m) else {
193 continue;
194 };
195 let mut stack = vec![code.as_sem_node()];
196 while let Some(n) = stack.pop() {
197 if let Some(s) = n.as_statement()
198 && crate::process::cfg::block_scopes(&s)
199 .iter()
200 .any(|block| block.members().any(|v| automatic_event(&v)))
201 {
202 return true;
203 }
204 // `if (e)`, `while (e)`: an event as a condition (15.5.5.3).
205 if let Some(s) = n.as_statement()
206 && let Some(mut c) = s.condition()
207 {
208 while c.kind() == ExpressionKind::Conversion
209 && let Some(x) = c.conversion_operand()
210 {
211 c = x;
212 }
213 if c.expr_type().is_some_and(|t| t.is_event()) {
214 return true;
215 }
216 }
217 if let Some(e) = n.as_expression() {
218 let event_typed = |x: Option<sv_lang::Expression<'_>>| {
219 x.and_then(|x| x.expr_type()).is_some_and(|t| t.is_event())
220 };
221 match e.kind() {
222 ExpressionKind::Assignment if event_typed(e.left()) => return true,
223 ExpressionKind::BinaryOp if event_typed(e.left()) || event_typed(e.right()) => {
224 return true;
225 }
226 ExpressionKind::UnaryOp if event_typed(e.operand()) => return true,
227 // `if (e)`: an event in a Boolean context (15.5.5.3).
228 ExpressionKind::Conversion
229 if event_typed(e.conversion_operand()) && !event_typed(Some(e)) =>
230 {
231 return true;
232 }
233 _ => {}
234 }
235 }
236 stack.extend(crate::process::cfg::live_children(&n));
237 }
238 }
239 false
240}